@gravity-ui/aikit 2.20.1 → 2.20.2-beta.1b9a51bfdf2818e8caefe25d7de3af232af29ca8.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/build/cjs/adapters/openai/helpers/buildFinalMessages.d.ts +1 -5
- package/build/cjs/adapters/openai/helpers/buildFinalMessages.js +1 -9
- package/build/cjs/adapters/openai/helpers/buildFinalMessages.js.map +1 -1
- package/build/cjs/adapters/openai/helpers/consumeOpenAIStream.d.ts +1 -2
- package/build/cjs/adapters/openai/helpers/consumeOpenAIStream.js +17 -27
- package/build/cjs/adapters/openai/helpers/consumeOpenAIStream.js.map +1 -1
- package/build/cjs/adapters/openai/useOpenAIResponsesAdapter.d.ts +0 -9
- package/build/cjs/adapters/openai/useOpenAIResponsesAdapter.js +8 -74
- package/build/cjs/adapters/openai/useOpenAIResponsesAdapter.js.map +1 -1
- package/build/cjs/components/atoms/SubmitButton/SubmitButton.css +1 -0
- package/build/cjs/hooks/useMobileControlSize.d.ts +1 -1
- package/build/cjs/hooks/useMobileControlSize.js.map +1 -1
- package/build/cjs/package.json +1 -1
- package/build/esm/adapters/openai/helpers/buildFinalMessages.d.ts +1 -5
- package/build/esm/adapters/openai/helpers/buildFinalMessages.js +1 -9
- package/build/esm/adapters/openai/helpers/buildFinalMessages.js.map +1 -1
- package/build/esm/adapters/openai/helpers/consumeOpenAIStream.d.ts +1 -2
- package/build/esm/adapters/openai/helpers/consumeOpenAIStream.js +17 -27
- package/build/esm/adapters/openai/helpers/consumeOpenAIStream.js.map +1 -1
- package/build/esm/adapters/openai/useOpenAIResponsesAdapter.d.ts +0 -9
- package/build/esm/adapters/openai/useOpenAIResponsesAdapter.js +8 -73
- package/build/esm/adapters/openai/useOpenAIResponsesAdapter.js.map +1 -1
- package/build/esm/components/atoms/SubmitButton/SubmitButton.css +1 -0
- package/build/esm/hooks/useMobileControlSize.d.ts +1 -1
- package/build/esm/hooks/useMobileControlSize.js.map +1 -1
- package/build/esm/package.json +1 -1
- package/package.json +2 -2
- package/build/cjs/adapters/openai/helpers/isMessageOutputItemDoneEvent.d.ts +0 -3
- package/build/cjs/adapters/openai/helpers/isMessageOutputItemDoneEvent.js +0 -22
- package/build/cjs/adapters/openai/helpers/isMessageOutputItemDoneEvent.js.map +0 -1
- package/build/esm/adapters/openai/helpers/isMessageOutputItemDoneEvent.d.ts +0 -3
- package/build/esm/adapters/openai/helpers/isMessageOutputItemDoneEvent.js +0 -19
- package/build/esm/adapters/openai/helpers/isMessageOutputItemDoneEvent.js.map +0 -1
|
@@ -1,10 +1,6 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { TChatMessage, TMessageContentUnion } from "../../../types/index.js";
|
|
2
2
|
export type BuildFinalMessagesParams = {
|
|
3
3
|
baseMessages: TChatMessage[];
|
|
4
|
-
completedAssistantMessages: {
|
|
5
|
-
id: string;
|
|
6
|
-
content: TAssistantMessage['content'];
|
|
7
|
-
}[];
|
|
8
4
|
currentAssistantMessageId: string;
|
|
9
5
|
contentParts: TMessageContentUnion[];
|
|
10
6
|
};
|
|
@@ -8,17 +8,9 @@ function isMessageContentEmpty(content) {
|
|
|
8
8
|
return Array.isArray(content) && content.length === 0;
|
|
9
9
|
}
|
|
10
10
|
function buildFinalMessages(params) {
|
|
11
|
-
const { baseMessages,
|
|
11
|
+
const { baseMessages, currentAssistantMessageId, contentParts } = params;
|
|
12
12
|
const currentContent = (0, contentPartsToMessageContent_1.contentPartsToMessageContent)(contentParts);
|
|
13
|
-
const completedWithContent = completedAssistantMessages.filter((m) => !isMessageContentEmpty(m.content));
|
|
14
13
|
const result = [...baseMessages];
|
|
15
|
-
for (const m of completedWithContent) {
|
|
16
|
-
result.push({
|
|
17
|
-
id: m.id,
|
|
18
|
-
role: 'assistant',
|
|
19
|
-
content: m.content,
|
|
20
|
-
});
|
|
21
|
-
}
|
|
22
14
|
if (!isMessageContentEmpty(currentContent)) {
|
|
23
15
|
result.push({
|
|
24
16
|
id: currentAssistantMessageId,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"buildFinalMessages.js","sourceRoot":"../../../../../src","sources":["adapters/openai/helpers/buildFinalMessages.ts"],"names":[],"mappings":";;
|
|
1
|
+
{"version":3,"file":"buildFinalMessages.js","sourceRoot":"../../../../../src","sources":["adapters/openai/helpers/buildFinalMessages.ts"],"names":[],"mappings":";;AAeA,gDAYC;AAzBD,oFAA4E;AAE5E,SAAS,qBAAqB,CAAC,OAAqC;IAChE,IAAI,OAAO,OAAO,KAAK,QAAQ;QAAE,OAAO,OAAO,CAAC,IAAI,EAAE,KAAK,EAAE,CAAC;IAC9D,OAAO,KAAK,CAAC,OAAO,CAAC,OAAO,CAAC,IAAI,OAAO,CAAC,MAAM,KAAK,CAAC,CAAC;AAC1D,CAAC;AAQD,SAAgB,kBAAkB,CAAC,MAAgC;IAC/D,MAAM,EAAC,YAAY,EAAE,yBAAyB,EAAE,YAAY,EAAC,GAAG,MAAM,CAAC;IACvE,MAAM,cAAc,GAAG,IAAA,2DAA4B,EAAC,YAAY,CAAC,CAAC;IAClE,MAAM,MAAM,GAAmB,CAAC,GAAG,YAAY,CAAC,CAAC;IACjD,IAAI,CAAC,qBAAqB,CAAC,cAAc,CAAC,EAAE,CAAC;QACzC,MAAM,CAAC,IAAI,CAAC;YACR,EAAE,EAAE,yBAAyB;YAC7B,IAAI,EAAE,WAAW;YACjB,OAAO,EAAE,cAAc;SACL,CAAC,CAAC;IAC5B,CAAC;IACD,OAAO,MAAM,CAAC;AAClB,CAAC","sourcesContent":["import type {TAssistantMessage, TChatMessage, TMessageContentUnion} from '../../../types';\n\nimport {contentPartsToMessageContent} from './contentPartsToMessageContent';\n\nfunction isMessageContentEmpty(content: TAssistantMessage['content']): boolean {\n if (typeof content === 'string') return content.trim() === '';\n return Array.isArray(content) && content.length === 0;\n}\n\nexport type BuildFinalMessagesParams = {\n baseMessages: TChatMessage[];\n currentAssistantMessageId: string;\n contentParts: TMessageContentUnion[];\n};\n\nexport function buildFinalMessages(params: BuildFinalMessagesParams): TChatMessage[] {\n const {baseMessages, currentAssistantMessageId, contentParts} = params;\n const currentContent = contentPartsToMessageContent(contentParts);\n const result: TChatMessage[] = [...baseMessages];\n if (!isMessageContentEmpty(currentContent)) {\n result.push({\n id: currentAssistantMessageId,\n role: 'assistant',\n content: currentContent,\n } as TAssistantMessage);\n }\n return result;\n}\n"]}
|
|
@@ -2,11 +2,10 @@ import type { TAssistantMessage, TChatMessage } from "../../../types/index.js";
|
|
|
2
2
|
import type { OpenAIStreamEventLike } from "../types/index.js";
|
|
3
3
|
export type ConsumeStreamCallbacks = {
|
|
4
4
|
baseMessages: TChatMessage[];
|
|
5
|
-
getAssistantMessageId: (
|
|
5
|
+
getAssistantMessageId: () => string;
|
|
6
6
|
/** When the API sends `response.output_item.added` for a message, exposes OpenAI item id (e.g. msg-cc-…) for reactions. */
|
|
7
7
|
onAssistantMessageIdResolved?: (previousId: string, openaiItemId: string) => void;
|
|
8
8
|
onContentUpdate: (messageId: string, content: TAssistantMessage['content']) => void;
|
|
9
|
-
onNewMessage: (messageId: string) => void;
|
|
10
9
|
onEnd: (finalMessages: TChatMessage[], status: 'done' | 'error', error?: Error) => void;
|
|
11
10
|
getIsCancelled: () => boolean;
|
|
12
11
|
};
|
|
@@ -8,7 +8,6 @@ const contentPartsToMessageContent_1 = require("./contentPartsToMessageContent.j
|
|
|
8
8
|
const getOpenAIMessageItemIdFromOutputItemAdded_1 = require("./getOpenAIMessageItemIdFromOutputItemAdded.js");
|
|
9
9
|
const getStreamEndResult_1 = require("./getStreamEndResult.js");
|
|
10
10
|
const getStreamEventContentUpdate_1 = require("./getStreamEventContentUpdate.js");
|
|
11
|
-
const isMessageOutputItemDoneEvent_1 = require("./isMessageOutputItemDoneEvent.js");
|
|
12
11
|
const isStreamEndOrErrorEvent_1 = require("./isStreamEndOrErrorEvent.js");
|
|
13
12
|
function waitForNextTask() {
|
|
14
13
|
return new Promise((resolve) => setTimeout(resolve, 0));
|
|
@@ -20,16 +19,27 @@ function shouldYieldAfterUpdate(update) {
|
|
|
20
19
|
/** Consumes stream, invokes callbacks. State/setters live in caller. */
|
|
21
20
|
async function consumeOpenAIStream(stream, callbacks) {
|
|
22
21
|
var _a, e_1, _b, _c;
|
|
23
|
-
const { baseMessages, getAssistantMessageId, onAssistantMessageIdResolved, onContentUpdate,
|
|
24
|
-
|
|
25
|
-
let
|
|
26
|
-
let currentAssistantMessageId = getAssistantMessageId(0);
|
|
22
|
+
const { baseMessages, getAssistantMessageId, onAssistantMessageIdResolved, onContentUpdate, onEnd, getIsCancelled, } = callbacks;
|
|
23
|
+
let currentAssistantMessageId = getAssistantMessageId();
|
|
24
|
+
let assistantMessageItemIdResolved = false;
|
|
27
25
|
let contentParts = [];
|
|
28
26
|
const applyContentToCurrentMessage = (parts) => {
|
|
29
27
|
if (getIsCancelled())
|
|
30
28
|
return;
|
|
31
29
|
onContentUpdate(currentAssistantMessageId, (0, contentPartsToMessageContent_1.contentPartsToMessageContent)(parts));
|
|
32
30
|
};
|
|
31
|
+
const resolveAssistantMessageId = (openaiMessageItemId) => {
|
|
32
|
+
if (assistantMessageItemIdResolved)
|
|
33
|
+
return;
|
|
34
|
+
assistantMessageItemIdResolved = true;
|
|
35
|
+
if (openaiMessageItemId === currentAssistantMessageId)
|
|
36
|
+
return;
|
|
37
|
+
const previousId = currentAssistantMessageId;
|
|
38
|
+
currentAssistantMessageId = openaiMessageItemId;
|
|
39
|
+
if (!getIsCancelled()) {
|
|
40
|
+
onAssistantMessageIdResolved === null || onAssistantMessageIdResolved === void 0 ? void 0 : onAssistantMessageIdResolved(previousId, openaiMessageItemId);
|
|
41
|
+
}
|
|
42
|
+
};
|
|
33
43
|
try {
|
|
34
44
|
try {
|
|
35
45
|
for (var _d = true, stream_1 = tslib_1.__asyncValues(stream), stream_1_1; stream_1_1 = await stream_1.next(), _a = stream_1_1.done, !_a; _d = true) {
|
|
@@ -39,18 +49,13 @@ async function consumeOpenAIStream(stream, callbacks) {
|
|
|
39
49
|
if (getIsCancelled())
|
|
40
50
|
return;
|
|
41
51
|
const openaiMessageItemId = (0, getOpenAIMessageItemIdFromOutputItemAdded_1.getOpenAIMessageItemIdFromOutputItemAdded)(event);
|
|
42
|
-
if (openaiMessageItemId
|
|
43
|
-
|
|
44
|
-
currentAssistantMessageId = openaiMessageItemId;
|
|
45
|
-
if (!getIsCancelled()) {
|
|
46
|
-
onAssistantMessageIdResolved === null || onAssistantMessageIdResolved === void 0 ? void 0 : onAssistantMessageIdResolved(previousId, openaiMessageItemId);
|
|
47
|
-
}
|
|
52
|
+
if (openaiMessageItemId) {
|
|
53
|
+
resolveAssistantMessageId(openaiMessageItemId);
|
|
48
54
|
continue;
|
|
49
55
|
}
|
|
50
56
|
if ((0, isStreamEndOrErrorEvent_1.isStreamEndOrErrorEvent)(event)) {
|
|
51
57
|
const finalMessages = (0, buildFinalMessages_1.buildFinalMessages)({
|
|
52
58
|
baseMessages,
|
|
53
|
-
completedAssistantMessages,
|
|
54
59
|
currentAssistantMessageId,
|
|
55
60
|
contentParts,
|
|
56
61
|
});
|
|
@@ -60,19 +65,6 @@ async function consumeOpenAIStream(stream, callbacks) {
|
|
|
60
65
|
onEnd(finalMessages, endResult.status, endResult.status === 'error' ? endResult.error : undefined);
|
|
61
66
|
return;
|
|
62
67
|
}
|
|
63
|
-
if ((0, isMessageOutputItemDoneEvent_1.isMessageOutputItemDoneEvent)(event)) {
|
|
64
|
-
completedAssistantMessages.push({
|
|
65
|
-
id: currentAssistantMessageId,
|
|
66
|
-
content: (0, contentPartsToMessageContent_1.contentPartsToMessageContent)(contentParts),
|
|
67
|
-
});
|
|
68
|
-
messageIndex += 1;
|
|
69
|
-
currentAssistantMessageId = getAssistantMessageId(messageIndex);
|
|
70
|
-
contentParts = [];
|
|
71
|
-
if (getIsCancelled())
|
|
72
|
-
return;
|
|
73
|
-
onNewMessage(currentAssistantMessageId);
|
|
74
|
-
continue;
|
|
75
|
-
}
|
|
76
68
|
const update = (0, getStreamEventContentUpdate_1.getStreamEventContentUpdate)(event);
|
|
77
69
|
if (!update)
|
|
78
70
|
continue;
|
|
@@ -98,7 +90,6 @@ async function consumeOpenAIStream(stream, callbacks) {
|
|
|
98
90
|
if (!getIsCancelled()) {
|
|
99
91
|
const finalMessages = (0, buildFinalMessages_1.buildFinalMessages)({
|
|
100
92
|
baseMessages,
|
|
101
|
-
completedAssistantMessages,
|
|
102
93
|
currentAssistantMessageId,
|
|
103
94
|
contentParts,
|
|
104
95
|
});
|
|
@@ -109,7 +100,6 @@ async function consumeOpenAIStream(stream, callbacks) {
|
|
|
109
100
|
if (!getIsCancelled()) {
|
|
110
101
|
const finalMessages = (0, buildFinalMessages_1.buildFinalMessages)({
|
|
111
102
|
baseMessages,
|
|
112
|
-
completedAssistantMessages,
|
|
113
103
|
currentAssistantMessageId,
|
|
114
104
|
contentParts,
|
|
115
105
|
});
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"consumeOpenAIStream.js","sourceRoot":"../../../../../src","sources":["adapters/openai/helpers/consumeOpenAIStream.ts"],"names":[],"mappings":";;
|
|
1
|
+
{"version":3,"file":"consumeOpenAIStream.js","sourceRoot":"../../../../../src","sources":["adapters/openai/helpers/consumeOpenAIStream.ts"],"names":[],"mappings":"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","sourcesContent":["import type {TAssistantMessage, TChatMessage, TMessageContentUnion} from '../../../types';\nimport type {OpenAIStreamEventLike} from '../types';\n\nimport {applyContentUpdate} from './applyContentUpdate';\nimport {buildFinalMessages} from './buildFinalMessages';\nimport {contentPartsToMessageContent} from './contentPartsToMessageContent';\nimport {getOpenAIMessageItemIdFromOutputItemAdded} from './getOpenAIMessageItemIdFromOutputItemAdded';\nimport {getStreamEndResult} from './getStreamEndResult';\nimport {getStreamEventContentUpdate} from './getStreamEventContentUpdate';\nimport {isStreamEndOrErrorEvent} from './isStreamEndOrErrorEvent';\n\nfunction waitForNextTask(): Promise<void> {\n return new Promise((resolve) => setTimeout(resolve, 0));\n}\n\nfunction shouldYieldAfterUpdate(update: ReturnType<typeof getStreamEventContentUpdate>): boolean {\n return update?.kind === 'tool_add' && (update.status ?? 'loading') === 'loading';\n}\n\nexport type ConsumeStreamCallbacks = {\n baseMessages: TChatMessage[];\n getAssistantMessageId: () => string;\n /** When the API sends `response.output_item.added` for a message, exposes OpenAI item id (e.g. msg-cc-…) for reactions. */\n onAssistantMessageIdResolved?: (previousId: string, openaiItemId: string) => void;\n onContentUpdate: (messageId: string, content: TAssistantMessage['content']) => void;\n onEnd: (finalMessages: TChatMessage[], status: 'done' | 'error', error?: Error) => void;\n getIsCancelled: () => boolean;\n};\n\n/** Consumes stream, invokes callbacks. State/setters live in caller. */\n\nexport async function consumeOpenAIStream(\n stream: AsyncIterable<OpenAIStreamEventLike>,\n callbacks: ConsumeStreamCallbacks,\n): Promise<void> {\n const {\n baseMessages,\n getAssistantMessageId,\n onAssistantMessageIdResolved,\n onContentUpdate,\n onEnd,\n getIsCancelled,\n } = callbacks;\n\n let currentAssistantMessageId = getAssistantMessageId();\n let assistantMessageItemIdResolved = false;\n let contentParts: TMessageContentUnion[] = [];\n\n const applyContentToCurrentMessage = (parts: TMessageContentUnion[]) => {\n if (getIsCancelled()) return;\n onContentUpdate(currentAssistantMessageId, contentPartsToMessageContent(parts));\n };\n\n const resolveAssistantMessageId = (openaiMessageItemId: string) => {\n if (assistantMessageItemIdResolved) return;\n assistantMessageItemIdResolved = true;\n if (openaiMessageItemId === currentAssistantMessageId) return;\n\n const previousId = currentAssistantMessageId;\n currentAssistantMessageId = openaiMessageItemId;\n if (!getIsCancelled()) {\n onAssistantMessageIdResolved?.(previousId, openaiMessageItemId);\n }\n };\n\n try {\n for await (const event of stream) {\n if (getIsCancelled()) return;\n\n const openaiMessageItemId = getOpenAIMessageItemIdFromOutputItemAdded(event);\n if (openaiMessageItemId) {\n resolveAssistantMessageId(openaiMessageItemId);\n continue;\n }\n\n if (isStreamEndOrErrorEvent(event)) {\n const finalMessages = buildFinalMessages({\n baseMessages,\n currentAssistantMessageId,\n contentParts,\n });\n const endResult = getStreamEndResult(event);\n if (getIsCancelled()) return;\n onEnd(\n finalMessages,\n endResult.status,\n endResult.status === 'error' ? endResult.error : undefined,\n );\n return;\n }\n\n const update = getStreamEventContentUpdate(event);\n if (!update) continue;\n\n const nextParts = applyContentUpdate(contentParts, update);\n if (nextParts !== null) {\n contentParts = nextParts;\n applyContentToCurrentMessage(contentParts);\n if (shouldYieldAfterUpdate(update)) {\n await waitForNextTask();\n if (getIsCancelled()) return;\n }\n }\n }\n\n if (!getIsCancelled()) {\n const finalMessages = buildFinalMessages({\n baseMessages,\n currentAssistantMessageId,\n contentParts,\n });\n onEnd(finalMessages, 'done');\n }\n } catch (err) {\n if (!getIsCancelled()) {\n const finalMessages = buildFinalMessages({\n baseMessages,\n currentAssistantMessageId,\n contentParts,\n });\n onEnd(finalMessages, 'error', err instanceof Error ? err : new Error(String(err)));\n }\n }\n}\n"]}
|
|
@@ -2,14 +2,5 @@ import type { TChatMessage } from "../../types/index.js";
|
|
|
2
2
|
import type { OpenAIStreamAdapterOptions, OpenAIStreamAdapterResult, OpenAIStreamSource } from "./types/index.js";
|
|
3
3
|
import { OpenAIResponseLike } from "./types/openAiTypes.js";
|
|
4
4
|
export type { OpenAIStreamAdapterOptions, OpenAIStreamAdapterResult, OpenAIStreamAdapterStatus, OpenAIStreamSource, } from "./types/index.js";
|
|
5
|
-
/**
|
|
6
|
-
* When the stream splits the reply into two consecutive assistant messages, the same tool call can
|
|
7
|
-
* appear in both. Moves matching tool parts into the earlier segment and drops the duplicate from
|
|
8
|
-
* the later one so the UI does not show orphaned repeats.
|
|
9
|
-
*
|
|
10
|
-
* @param messages - Chat messages (possibly with duplicate tool parts across adjacent assistants).
|
|
11
|
-
* @returns A shallow-copied message list with orphan duplicates collapsed.
|
|
12
|
-
*/
|
|
13
|
-
export declare function collapseOrphanToolBetweenAssistantSegments(messages: TChatMessage[]): TChatMessage[];
|
|
14
5
|
export declare function useOpenAIResponsesAdapter(response: OpenAIResponseLike | null): TChatMessage[];
|
|
15
6
|
export declare function useOpenAIStreamAdapter(stream: OpenAIStreamSource | null, options?: OpenAIStreamAdapterOptions): OpenAIStreamAdapterResult;
|
|
@@ -1,70 +1,17 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.collapseOrphanToolBetweenAssistantSegments = collapseOrphanToolBetweenAssistantSegments;
|
|
4
3
|
exports.useOpenAIResponsesAdapter = useOpenAIResponsesAdapter;
|
|
5
4
|
exports.useOpenAIStreamAdapter = useOpenAIStreamAdapter;
|
|
6
5
|
const tslib_1 = require("tslib");
|
|
7
6
|
const react_1 = require("react");
|
|
8
7
|
const consumeOpenAIStream_1 = require("./helpers/consumeOpenAIStream.js");
|
|
9
|
-
const contentPartsToMessageContent_1 = require("./helpers/contentPartsToMessageContent.js");
|
|
10
8
|
const fetchResponseToStreamEvents_1 = require("./helpers/fetchResponseToStreamEvents.js");
|
|
11
9
|
const isFetchResponse_1 = require("./helpers/isFetchResponse.js");
|
|
12
10
|
const openAIResponseToMessages_1 = require("./helpers/openAIResponseToMessages.js");
|
|
13
11
|
/** Single non-streaming response → TChatMessage[]. For streaming use useOpenAIStreamAdapter. */
|
|
14
|
-
function isToolPart(p) {
|
|
15
|
-
return p.type === 'tool';
|
|
16
|
-
}
|
|
17
|
-
/**
|
|
18
|
-
* When the stream splits the reply into two consecutive assistant messages, the same tool call can
|
|
19
|
-
* appear in both. Moves matching tool parts into the earlier segment and drops the duplicate from
|
|
20
|
-
* the later one so the UI does not show orphaned repeats.
|
|
21
|
-
*
|
|
22
|
-
* @param messages - Chat messages (possibly with duplicate tool parts across adjacent assistants).
|
|
23
|
-
* @returns A shallow-copied message list with orphan duplicates collapsed.
|
|
24
|
-
*/
|
|
25
|
-
function collapseOrphanToolBetweenAssistantSegments(messages) {
|
|
26
|
-
const result = messages.map((m) => m.role === 'assistant' ? Object.assign({}, m) : m);
|
|
27
|
-
for (let i = result.length - 1; i >= 1; i--) {
|
|
28
|
-
const next = result[i];
|
|
29
|
-
const prev = result[i - 1];
|
|
30
|
-
if (prev.role !== 'assistant' || next.role !== 'assistant') {
|
|
31
|
-
continue;
|
|
32
|
-
}
|
|
33
|
-
const nextContent = next.content;
|
|
34
|
-
if (!Array.isArray(nextContent)) {
|
|
35
|
-
continue;
|
|
36
|
-
}
|
|
37
|
-
const prevContent = prev.content;
|
|
38
|
-
if (!Array.isArray(prevContent)) {
|
|
39
|
-
continue;
|
|
40
|
-
}
|
|
41
|
-
const updatedNextParts = [...nextContent];
|
|
42
|
-
let updatedPrevParts = [...prevContent];
|
|
43
|
-
let changed = false;
|
|
44
|
-
for (let t = updatedNextParts.length - 1; t >= 0; t--) {
|
|
45
|
-
const part = updatedNextParts[t];
|
|
46
|
-
if (!isToolPart(part) || !part.id) {
|
|
47
|
-
continue;
|
|
48
|
-
}
|
|
49
|
-
const pIdx = updatedPrevParts.findIndex((p) => isToolPart(p) && p.id === part.id);
|
|
50
|
-
if (pIdx < 0) {
|
|
51
|
-
continue;
|
|
52
|
-
}
|
|
53
|
-
updatedPrevParts = updatedPrevParts.map((p, j) => (j === pIdx ? part : p));
|
|
54
|
-
updatedNextParts.splice(t, 1);
|
|
55
|
-
changed = true;
|
|
56
|
-
}
|
|
57
|
-
if (!changed) {
|
|
58
|
-
continue;
|
|
59
|
-
}
|
|
60
|
-
result[i - 1] = Object.assign(Object.assign({}, prev), { content: (0, contentPartsToMessageContent_1.contentPartsToMessageContent)(updatedPrevParts) });
|
|
61
|
-
result[i] = Object.assign(Object.assign({}, next), { content: (0, contentPartsToMessageContent_1.contentPartsToMessageContent)(updatedNextParts) });
|
|
62
|
-
}
|
|
63
|
-
return result;
|
|
64
|
-
}
|
|
65
12
|
/**
|
|
66
13
|
* Index of the assistant message that should receive streamed content for `messageId`.
|
|
67
|
-
* Uses an exact id match when present; otherwise the last assistant (current streaming
|
|
14
|
+
* Uses an exact id match when present; otherwise the last assistant (current streaming message),
|
|
68
15
|
* which covers events that reference an id before our list has caught up.
|
|
69
16
|
*
|
|
70
17
|
* @param messages - Current chat messages while streaming.
|
|
@@ -166,11 +113,11 @@ function useOpenAIStreamAdapter(stream, options = {}) {
|
|
|
166
113
|
},
|
|
167
114
|
};
|
|
168
115
|
const baseMessages = initialMessagesRef.current;
|
|
169
|
-
const getAssistantMessageId = (
|
|
116
|
+
const getAssistantMessageId = () => assistantMessageId;
|
|
170
117
|
setMessages([
|
|
171
118
|
...baseMessages,
|
|
172
119
|
{
|
|
173
|
-
id: getAssistantMessageId(
|
|
120
|
+
id: getAssistantMessageId(),
|
|
174
121
|
role: 'assistant',
|
|
175
122
|
content: '',
|
|
176
123
|
},
|
|
@@ -192,12 +139,12 @@ function useOpenAIStreamAdapter(stream, options = {}) {
|
|
|
192
139
|
onAssistantMessageIdResolved: (previousId, openaiItemId) => {
|
|
193
140
|
if (cancelled)
|
|
194
141
|
return;
|
|
195
|
-
setMessages((prev) =>
|
|
142
|
+
setMessages((prev) => prev.map((msg) => {
|
|
196
143
|
if (msg.id === previousId && msg.role === 'assistant') {
|
|
197
144
|
return Object.assign(Object.assign({}, msg), { id: openaiItemId });
|
|
198
145
|
}
|
|
199
146
|
return msg;
|
|
200
|
-
}))
|
|
147
|
+
}));
|
|
201
148
|
},
|
|
202
149
|
onContentUpdate: (messageId, content) => {
|
|
203
150
|
if (cancelled)
|
|
@@ -205,34 +152,21 @@ function useOpenAIStreamAdapter(stream, options = {}) {
|
|
|
205
152
|
setMessages((prev) => {
|
|
206
153
|
const targetIndex = resolveStreamContentTargetIndex(prev, messageId);
|
|
207
154
|
if (targetIndex < 0) {
|
|
208
|
-
return
|
|
155
|
+
return prev;
|
|
209
156
|
}
|
|
210
|
-
|
|
157
|
+
return prev.map((msg, i) => {
|
|
211
158
|
if (i !== targetIndex) {
|
|
212
159
|
return msg;
|
|
213
160
|
}
|
|
214
161
|
return Object.assign(Object.assign({}, msg), { content });
|
|
215
162
|
});
|
|
216
|
-
return collapseOrphanToolBetweenAssistantSegments(mapped);
|
|
217
163
|
});
|
|
218
164
|
},
|
|
219
|
-
onNewMessage: (messageId) => {
|
|
220
|
-
if (cancelled)
|
|
221
|
-
return;
|
|
222
|
-
setMessages((prev) => [
|
|
223
|
-
...prev,
|
|
224
|
-
{
|
|
225
|
-
id: messageId,
|
|
226
|
-
role: 'assistant',
|
|
227
|
-
content: '',
|
|
228
|
-
},
|
|
229
|
-
]);
|
|
230
|
-
},
|
|
231
165
|
onEnd: (finalMessages, s, err) => {
|
|
232
166
|
var _a;
|
|
233
167
|
setStatus(s === 'done' ? 'ready' : s);
|
|
234
168
|
setError(err !== null && err !== void 0 ? err : null);
|
|
235
|
-
const normalized = injectUsageMetadata(
|
|
169
|
+
const normalized = injectUsageMetadata(finalMessages);
|
|
236
170
|
if (!cancelled) {
|
|
237
171
|
setMessages(normalized);
|
|
238
172
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useOpenAIResponsesAdapter.js","sourceRoot":"../../../../src","sources":["adapters/openai/useOpenAIResponsesAdapter.ts"],"names":[],"mappings":"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","sourcesContent":["import {useEffect, useMemo, useRef, useState} from 'react';\n\nimport type {TAssistantMessage, TChatMessage, TMessageContentUnion} from '../../types';\n\nimport {type ConsumeStreamCallbacks, consumeOpenAIStream} from './helpers/consumeOpenAIStream';\nimport {contentPartsToMessageContent} from './helpers/contentPartsToMessageContent';\nimport {fetchResponseToStreamEvents} from './helpers/fetchResponseToStreamEvents';\nimport {isFetchResponse} from './helpers/isFetchResponse';\nimport {openAIResponseToMessages} from './helpers/openAIResponseToMessages';\nimport type {\n OpenAIStreamAdapterOptions,\n OpenAIStreamAdapterResult,\n OpenAIStreamSource,\n} from './types';\nimport {OpenAIResponseLike, OpenAIStreamEventLike} from './types/openAiTypes';\n\nexport type {\n OpenAIStreamAdapterOptions,\n OpenAIStreamAdapterResult,\n OpenAIStreamAdapterStatus,\n OpenAIStreamSource,\n} from './types';\n\n/** Single non-streaming response → TChatMessage[]. For streaming use useOpenAIStreamAdapter. */\n\nfunction isToolPart(p: TMessageContentUnion): p is Extract<TMessageContentUnion, {type: 'tool'}> {\n return p.type === 'tool';\n}\n\n/**\n * When the stream splits the reply into two consecutive assistant messages, the same tool call can\n * appear in both. Moves matching tool parts into the earlier segment and drops the duplicate from\n * the later one so the UI does not show orphaned repeats.\n *\n * @param messages - Chat messages (possibly with duplicate tool parts across adjacent assistants).\n * @returns A shallow-copied message list with orphan duplicates collapsed.\n */\nexport function collapseOrphanToolBetweenAssistantSegments(\n messages: TChatMessage[],\n): TChatMessage[] {\n const result = messages.map((m) =>\n m.role === 'assistant' ? ({...m} as TAssistantMessage) : m,\n );\n\n for (let i = result.length - 1; i >= 1; i--) {\n const next = result[i] as TAssistantMessage;\n const prev = result[i - 1] as TAssistantMessage;\n if (prev.role !== 'assistant' || next.role !== 'assistant') {\n continue;\n }\n\n const nextContent = next.content;\n if (!Array.isArray(nextContent)) {\n continue;\n }\n\n const prevContent = prev.content;\n if (!Array.isArray(prevContent)) {\n continue;\n }\n\n const updatedNextParts = [...nextContent] as TMessageContentUnion[];\n let updatedPrevParts = [...prevContent] as TMessageContentUnion[];\n let changed = false;\n\n for (let t = updatedNextParts.length - 1; t >= 0; t--) {\n const part = updatedNextParts[t];\n if (!isToolPart(part) || !part.id) {\n continue;\n }\n const pIdx = updatedPrevParts.findIndex((p) => isToolPart(p) && p.id === part.id);\n if (pIdx < 0) {\n continue;\n }\n updatedPrevParts = updatedPrevParts.map((p, j) => (j === pIdx ? part : p));\n updatedNextParts.splice(t, 1);\n changed = true;\n }\n\n if (!changed) {\n continue;\n }\n\n result[i - 1] = {\n ...prev,\n content: contentPartsToMessageContent(updatedPrevParts),\n };\n result[i] = {\n ...next,\n content: contentPartsToMessageContent(updatedNextParts),\n };\n }\n\n return result;\n}\n\n/**\n * Index of the assistant message that should receive streamed content for `messageId`.\n * Uses an exact id match when present; otherwise the last assistant (current streaming segment),\n * which covers events that reference an id before our list has caught up.\n *\n * @param messages - Current chat messages while streaming.\n * @param messageId - Target assistant id from the stream event.\n * @returns Index of the assistant to update, or `-1` if there is no assistant.\n */\nfunction resolveStreamContentTargetIndex(messages: TChatMessage[], messageId: string): number {\n const byId = messages.findIndex((m) => m.id === messageId && m.role === 'assistant');\n if (byId >= 0) {\n return byId;\n }\n for (let i = messages.length - 1; i >= 0; i--) {\n if (messages[i].role === 'assistant') {\n return i;\n }\n }\n return -1;\n}\n\nfunction getResponseIdFromEvent(event: OpenAIStreamEventLike): string | null {\n const response = (event as {response?: unknown}).response;\n if (!response || typeof response !== 'object') {\n return null;\n }\n\n const id = (response as {id?: unknown}).id;\n return typeof id === 'string' ? id : null;\n}\n\nexport function useOpenAIResponsesAdapter(response: OpenAIResponseLike | null): TChatMessage[] {\n return useMemo(() => openAIResponseToMessages(response), [response]);\n}\n\nexport function useOpenAIStreamAdapter(\n stream: OpenAIStreamSource | null,\n options: OpenAIStreamAdapterOptions = {},\n): OpenAIStreamAdapterResult {\n const {\n initialMessages = [],\n assistantMessageId: optionId,\n onStreamEnd,\n trackTokenUsage = false,\n } = options;\n const onStreamEndRef = useRef(onStreamEnd);\n onStreamEndRef.current = onStreamEnd;\n const initialMessagesRef = useRef(initialMessages);\n initialMessagesRef.current = initialMessages;\n\n // messageId → output_tokens from response.completed event\n const usageMapRef = useRef<Map<string, number>>(new Map());\n\n const [messages, setMessages] = useState<TChatMessage[]>(initialMessages);\n const [status, setStatus] = useState<OpenAIStreamAdapterResult['status']>('ready');\n const [error, setError] = useState<Error | null>(null);\n const [responseId, setResponseId] = useState<string | null>(null);\n\n const assistantMessageId = useMemo(() => optionId ?? `assistant-${Date.now()}`, [optionId]);\n\n useEffect(() => {\n if (!stream) {\n setStatus('ready');\n setError(null);\n setResponseId(null);\n return undefined;\n }\n\n const sourceStream: AsyncIterable<OpenAIStreamEventLike> = isFetchResponse(stream)\n ? fetchResponseToStreamEvents(stream)\n : stream;\n\n let cancelled = false;\n setStatus('streaming');\n setError(null);\n setResponseId(null);\n usageMapRef.current = new Map();\n\n const streamToConsume: AsyncIterable<OpenAIStreamEventLike> = {\n async *[Symbol.asyncIterator]() {\n for await (const event of sourceStream) {\n const nextResponseId = getResponseIdFromEvent(event);\n if (!cancelled && nextResponseId) {\n setResponseId((prev) => (prev === nextResponseId ? prev : nextResponseId));\n }\n\n // Extract token usage from response.completed (only when opt-in)\n if (trackTokenUsage) {\n const e = event as Record<string, unknown>;\n const eventType =\n (e.type as string | undefined) ?? (e.event as string | undefined);\n if (eventType === 'response.completed' && e.response) {\n const response = e.response as {\n usage?: {output_tokens?: number};\n output?: Array<{id?: string; type?: string}>;\n };\n const tokens = response.usage?.output_tokens ?? 0;\n if (tokens > 0) {\n (response.output ?? []).forEach(({id, type}) => {\n if (id && type === 'message') {\n usageMapRef.current.set(id, tokens);\n }\n });\n }\n }\n }\n\n yield event;\n }\n },\n };\n\n const baseMessages = initialMessagesRef.current;\n const getAssistantMessageId = (index: number) =>\n index === 0 ? assistantMessageId : `${assistantMessageId}-${index}`;\n\n setMessages([\n ...baseMessages,\n {\n id: getAssistantMessageId(0),\n role: 'assistant',\n content: '',\n } as TAssistantMessage,\n ]);\n\n const injectUsageMetadata = (msgs: TChatMessage[]): TChatMessage[] => {\n if (usageMapRef.current.size === 0) return msgs;\n return msgs.map((msg) => {\n if (msg.role === 'assistant' && msg.id && usageMapRef.current.has(msg.id)) {\n return {\n ...msg,\n metadata: {\n ...msg.metadata,\n outputTokens: usageMapRef.current.get(msg.id),\n },\n } as TAssistantMessage;\n }\n return msg;\n });\n };\n\n const callbacks: ConsumeStreamCallbacks = {\n baseMessages,\n getAssistantMessageId,\n // Provisional id from getAssistantMessageId → OpenAI item id once the stream provides it.\n onAssistantMessageIdResolved: (previousId, openaiItemId) => {\n if (cancelled) return;\n setMessages((prev) =>\n collapseOrphanToolBetweenAssistantSegments(\n prev.map((msg): TChatMessage => {\n if (msg.id === previousId && msg.role === 'assistant') {\n return {...msg, id: openaiItemId} as TAssistantMessage;\n }\n return msg;\n }),\n ),\n );\n },\n onContentUpdate: (messageId, content) => {\n if (cancelled) return;\n setMessages((prev) => {\n const targetIndex = resolveStreamContentTargetIndex(prev, messageId);\n if (targetIndex < 0) {\n return collapseOrphanToolBetweenAssistantSegments(prev);\n }\n const mapped = prev.map((msg, i) => {\n if (i !== targetIndex) {\n return msg;\n }\n return {...msg, content} as TAssistantMessage;\n });\n return collapseOrphanToolBetweenAssistantSegments(mapped);\n });\n },\n onNewMessage: (messageId) => {\n if (cancelled) return;\n setMessages((prev) => [\n ...prev,\n {\n id: messageId,\n role: 'assistant',\n content: '',\n } as TAssistantMessage,\n ]);\n },\n onEnd: (finalMessages, s, err) => {\n setStatus(s === 'done' ? 'ready' : s);\n setError(err ?? null);\n const normalized = injectUsageMetadata(\n collapseOrphanToolBetweenAssistantSegments(finalMessages),\n );\n if (!cancelled) {\n setMessages(normalized);\n }\n onStreamEndRef.current?.(normalized);\n },\n getIsCancelled: () => cancelled,\n };\n\n consumeOpenAIStream(streamToConsume, callbacks);\n\n return () => {\n cancelled = true;\n };\n }, [stream, assistantMessageId]);\n\n return {messages, status, error, responseId};\n}\n"]}
|
|
1
|
+
{"version":3,"file":"useOpenAIResponsesAdapter.js","sourceRoot":"../../../../src","sources":["adapters/openai/useOpenAIResponsesAdapter.ts"],"names":[],"mappings":"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","sourcesContent":["import {useEffect, useMemo, useRef, useState} from 'react';\n\nimport type {TAssistantMessage, TChatMessage} from '../../types';\n\nimport {type ConsumeStreamCallbacks, consumeOpenAIStream} from './helpers/consumeOpenAIStream';\nimport {fetchResponseToStreamEvents} from './helpers/fetchResponseToStreamEvents';\nimport {isFetchResponse} from './helpers/isFetchResponse';\nimport {openAIResponseToMessages} from './helpers/openAIResponseToMessages';\nimport type {\n OpenAIStreamAdapterOptions,\n OpenAIStreamAdapterResult,\n OpenAIStreamSource,\n} from './types';\nimport {OpenAIResponseLike, OpenAIStreamEventLike} from './types/openAiTypes';\n\nexport type {\n OpenAIStreamAdapterOptions,\n OpenAIStreamAdapterResult,\n OpenAIStreamAdapterStatus,\n OpenAIStreamSource,\n} from './types';\n\n/** Single non-streaming response → TChatMessage[]. For streaming use useOpenAIStreamAdapter. */\n\n/**\n * Index of the assistant message that should receive streamed content for `messageId`.\n * Uses an exact id match when present; otherwise the last assistant (current streaming message),\n * which covers events that reference an id before our list has caught up.\n *\n * @param messages - Current chat messages while streaming.\n * @param messageId - Target assistant id from the stream event.\n * @returns Index of the assistant to update, or `-1` if there is no assistant.\n */\nfunction resolveStreamContentTargetIndex(messages: TChatMessage[], messageId: string): number {\n const byId = messages.findIndex((m) => m.id === messageId && m.role === 'assistant');\n if (byId >= 0) {\n return byId;\n }\n for (let i = messages.length - 1; i >= 0; i--) {\n if (messages[i].role === 'assistant') {\n return i;\n }\n }\n return -1;\n}\n\nfunction getResponseIdFromEvent(event: OpenAIStreamEventLike): string | null {\n const response = (event as {response?: unknown}).response;\n if (!response || typeof response !== 'object') {\n return null;\n }\n\n const id = (response as {id?: unknown}).id;\n return typeof id === 'string' ? id : null;\n}\n\nexport function useOpenAIResponsesAdapter(response: OpenAIResponseLike | null): TChatMessage[] {\n return useMemo(() => openAIResponseToMessages(response), [response]);\n}\n\nexport function useOpenAIStreamAdapter(\n stream: OpenAIStreamSource | null,\n options: OpenAIStreamAdapterOptions = {},\n): OpenAIStreamAdapterResult {\n const {\n initialMessages = [],\n assistantMessageId: optionId,\n onStreamEnd,\n trackTokenUsage = false,\n } = options;\n const onStreamEndRef = useRef(onStreamEnd);\n onStreamEndRef.current = onStreamEnd;\n const initialMessagesRef = useRef(initialMessages);\n initialMessagesRef.current = initialMessages;\n\n // messageId → output_tokens from response.completed event\n const usageMapRef = useRef<Map<string, number>>(new Map());\n\n const [messages, setMessages] = useState<TChatMessage[]>(initialMessages);\n const [status, setStatus] = useState<OpenAIStreamAdapterResult['status']>('ready');\n const [error, setError] = useState<Error | null>(null);\n const [responseId, setResponseId] = useState<string | null>(null);\n\n const assistantMessageId = useMemo(() => optionId ?? `assistant-${Date.now()}`, [optionId]);\n\n useEffect(() => {\n if (!stream) {\n setStatus('ready');\n setError(null);\n setResponseId(null);\n return undefined;\n }\n\n const sourceStream: AsyncIterable<OpenAIStreamEventLike> = isFetchResponse(stream)\n ? fetchResponseToStreamEvents(stream)\n : stream;\n\n let cancelled = false;\n setStatus('streaming');\n setError(null);\n setResponseId(null);\n usageMapRef.current = new Map();\n\n const streamToConsume: AsyncIterable<OpenAIStreamEventLike> = {\n async *[Symbol.asyncIterator]() {\n for await (const event of sourceStream) {\n const nextResponseId = getResponseIdFromEvent(event);\n if (!cancelled && nextResponseId) {\n setResponseId((prev) => (prev === nextResponseId ? prev : nextResponseId));\n }\n\n // Extract token usage from response.completed (only when opt-in)\n if (trackTokenUsage) {\n const e = event as Record<string, unknown>;\n const eventType =\n (e.type as string | undefined) ?? (e.event as string | undefined);\n if (eventType === 'response.completed' && e.response) {\n const response = e.response as {\n usage?: {output_tokens?: number};\n output?: Array<{id?: string; type?: string}>;\n };\n const tokens = response.usage?.output_tokens ?? 0;\n if (tokens > 0) {\n (response.output ?? []).forEach(({id, type}) => {\n if (id && type === 'message') {\n usageMapRef.current.set(id, tokens);\n }\n });\n }\n }\n }\n\n yield event;\n }\n },\n };\n\n const baseMessages = initialMessagesRef.current;\n const getAssistantMessageId = () => assistantMessageId;\n\n setMessages([\n ...baseMessages,\n {\n id: getAssistantMessageId(),\n role: 'assistant',\n content: '',\n } as TAssistantMessage,\n ]);\n\n const injectUsageMetadata = (msgs: TChatMessage[]): TChatMessage[] => {\n if (usageMapRef.current.size === 0) return msgs;\n return msgs.map((msg) => {\n if (msg.role === 'assistant' && msg.id && usageMapRef.current.has(msg.id)) {\n return {\n ...msg,\n metadata: {\n ...msg.metadata,\n outputTokens: usageMapRef.current.get(msg.id),\n },\n } as TAssistantMessage;\n }\n return msg;\n });\n };\n\n const callbacks: ConsumeStreamCallbacks = {\n baseMessages,\n getAssistantMessageId,\n // Provisional id from getAssistantMessageId → OpenAI item id once the stream provides it.\n onAssistantMessageIdResolved: (previousId, openaiItemId) => {\n if (cancelled) return;\n setMessages((prev) =>\n prev.map((msg): TChatMessage => {\n if (msg.id === previousId && msg.role === 'assistant') {\n return {...msg, id: openaiItemId} as TAssistantMessage;\n }\n return msg;\n }),\n );\n },\n onContentUpdate: (messageId, content) => {\n if (cancelled) return;\n setMessages((prev) => {\n const targetIndex = resolveStreamContentTargetIndex(prev, messageId);\n if (targetIndex < 0) {\n return prev;\n }\n return prev.map((msg, i) => {\n if (i !== targetIndex) {\n return msg;\n }\n return {...msg, content} as TAssistantMessage;\n });\n });\n },\n onEnd: (finalMessages, s, err) => {\n setStatus(s === 'done' ? 'ready' : s);\n setError(err ?? null);\n const normalized = injectUsageMetadata(finalMessages);\n if (!cancelled) {\n setMessages(normalized);\n }\n onStreamEndRef.current?.(normalized);\n },\n getIsCancelled: () => cancelled,\n };\n\n consumeOpenAIStream(streamToConsume, callbacks);\n\n return () => {\n cancelled = true;\n };\n }, [stream, assistantMessageId]);\n\n return {messages, status, error, responseId};\n}\n"]}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export type ControlSize = 'xs' | 's' | 'm' | 'l' | 'xl';
|
|
1
|
+
export type ControlSize = 'xxs' | 'xs' | 's' | 'm' | 'l' | 'xl';
|
|
2
2
|
/**
|
|
3
3
|
* Resolves a control size: an explicit size always wins, otherwise the mobile
|
|
4
4
|
* default is used in mobile mode and the desktop default elsewhere.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useMobileControlSize.js","sourceRoot":"../../../src","sources":["hooks/useMobileControlSize.ts"],"names":[],"mappings":";;
|
|
1
|
+
{"version":3,"file":"useMobileControlSize.js","sourceRoot":"../../../src","sources":["hooks/useMobileControlSize.ts"],"names":[],"mappings":";;AAUA,4DASC;AAKD,oDAQC;AAKD,gDAEC;AAvCD,6CAA4C;AAM5C;;;GAGG;AACH,SAAgB,wBAAwB,CAAwB,MAK/D;IACG,MAAM,EAAC,IAAI,EAAE,cAAc,EAAE,aAAa,EAAE,QAAQ,EAAC,GAAG,MAAM,CAAC;IAE/D,OAAO,IAAI,aAAJ,IAAI,cAAJ,IAAI,GAAI,CAAC,QAAQ,CAAC,CAAC,CAAC,aAAa,CAAC,CAAC,CAAC,cAAc,CAAC,CAAC;AAC/D,CAAC;AAED;;GAEG;AACH,SAAgB,oBAAoB,CAChC,IAAmB,EACnB,cAAiB,EACjB,aAAgB;IAEhB,MAAM,QAAQ,GAAG,IAAA,iBAAS,GAAE,CAAC;IAE7B,OAAO,wBAAwB,CAAC,EAAC,IAAI,EAAE,cAAc,EAAE,aAAa,EAAE,QAAQ,EAAC,CAAC,CAAC;AACrF,CAAC;AAED;;GAEG;AACH,SAAgB,kBAAkB,CAAC,IAA6B;IAC5D,OAAO,IAAI,KAAK,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;AACnC,CAAC","sourcesContent":["import {useMobile} from '@gravity-ui/uikit';\n\n// `xxs` only exists on `Label`, which uikit 7.48 gained. It is listed here so that the helpers\n// stay usable with the whole `LabelProps['size']` union across the supported uikit range.\nexport type ControlSize = 'xxs' | 'xs' | 's' | 'm' | 'l' | 'xl';\n\n/**\n * Resolves a control size: an explicit size always wins, otherwise the mobile\n * default is used in mobile mode and the desktop default elsewhere.\n */\nexport function resolveMobileControlSize<T extends ControlSize>(params: {\n size: T | undefined;\n desktopDefault: T;\n mobileDefault: T;\n isMobile: boolean;\n}): T {\n const {size, desktopDefault, mobileDefault, isMobile} = params;\n\n return size ?? (isMobile ? mobileDefault : desktopDefault);\n}\n\n/**\n * Mobile-aware control size, driven by the uikit `MobileProvider` context.\n */\nexport function useMobileControlSize<T extends ControlSize>(\n size: T | undefined,\n desktopDefault: T,\n mobileDefault: T,\n): T {\n const isMobile = useMobile();\n\n return resolveMobileControlSize({size, desktopDefault, mobileDefault, isMobile});\n}\n\n/**\n * Icon size matching the resolved control size.\n */\nexport function getControlIconSize(size: ControlSize | undefined): number {\n return size === 'xl' ? 20 : 16;\n}\n"]}
|
package/build/cjs/package.json
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":"2.20.
|
|
1
|
+
{"version":"2.20.2-beta.1b9a51bfdf2818e8caefe25d7de3af232af29ca8.0","type":"commonjs","sideEffects":["*.css","*.scss"]}
|
|
@@ -1,10 +1,6 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { TChatMessage, TMessageContentUnion } from "../../../types/index.js";
|
|
2
2
|
export type BuildFinalMessagesParams = {
|
|
3
3
|
baseMessages: TChatMessage[];
|
|
4
|
-
completedAssistantMessages: {
|
|
5
|
-
id: string;
|
|
6
|
-
content: TAssistantMessage['content'];
|
|
7
|
-
}[];
|
|
8
4
|
currentAssistantMessageId: string;
|
|
9
5
|
contentParts: TMessageContentUnion[];
|
|
10
6
|
};
|
|
@@ -5,17 +5,9 @@ function isMessageContentEmpty(content) {
|
|
|
5
5
|
return Array.isArray(content) && content.length === 0;
|
|
6
6
|
}
|
|
7
7
|
export function buildFinalMessages(params) {
|
|
8
|
-
const { baseMessages,
|
|
8
|
+
const { baseMessages, currentAssistantMessageId, contentParts } = params;
|
|
9
9
|
const currentContent = contentPartsToMessageContent(contentParts);
|
|
10
|
-
const completedWithContent = completedAssistantMessages.filter((m) => !isMessageContentEmpty(m.content));
|
|
11
10
|
const result = [...baseMessages];
|
|
12
|
-
for (const m of completedWithContent) {
|
|
13
|
-
result.push({
|
|
14
|
-
id: m.id,
|
|
15
|
-
role: 'assistant',
|
|
16
|
-
content: m.content,
|
|
17
|
-
});
|
|
18
|
-
}
|
|
19
11
|
if (!isMessageContentEmpty(currentContent)) {
|
|
20
12
|
result.push({
|
|
21
13
|
id: currentAssistantMessageId,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"buildFinalMessages.js","sourceRoot":"../../../../../src","sources":["adapters/openai/helpers/buildFinalMessages.ts"],"names":[],"mappings":"AAEA,OAAO,EAAC,4BAA4B,EAAC,0CAAuC;AAE5E,SAAS,qBAAqB,CAAC,OAAqC;IAChE,IAAI,OAAO,OAAO,KAAK,QAAQ;QAAE,OAAO,OAAO,CAAC,IAAI,EAAE,KAAK,EAAE,CAAC;IAC9D,OAAO,KAAK,CAAC,OAAO,CAAC,OAAO,CAAC,IAAI,OAAO,CAAC,MAAM,KAAK,CAAC,CAAC;AAC1D,CAAC;
|
|
1
|
+
{"version":3,"file":"buildFinalMessages.js","sourceRoot":"../../../../../src","sources":["adapters/openai/helpers/buildFinalMessages.ts"],"names":[],"mappings":"AAEA,OAAO,EAAC,4BAA4B,EAAC,0CAAuC;AAE5E,SAAS,qBAAqB,CAAC,OAAqC;IAChE,IAAI,OAAO,OAAO,KAAK,QAAQ;QAAE,OAAO,OAAO,CAAC,IAAI,EAAE,KAAK,EAAE,CAAC;IAC9D,OAAO,KAAK,CAAC,OAAO,CAAC,OAAO,CAAC,IAAI,OAAO,CAAC,MAAM,KAAK,CAAC,CAAC;AAC1D,CAAC;AAQD,MAAM,UAAU,kBAAkB,CAAC,MAAgC;IAC/D,MAAM,EAAC,YAAY,EAAE,yBAAyB,EAAE,YAAY,EAAC,GAAG,MAAM,CAAC;IACvE,MAAM,cAAc,GAAG,4BAA4B,CAAC,YAAY,CAAC,CAAC;IAClE,MAAM,MAAM,GAAmB,CAAC,GAAG,YAAY,CAAC,CAAC;IACjD,IAAI,CAAC,qBAAqB,CAAC,cAAc,CAAC,EAAE,CAAC;QACzC,MAAM,CAAC,IAAI,CAAC;YACR,EAAE,EAAE,yBAAyB;YAC7B,IAAI,EAAE,WAAW;YACjB,OAAO,EAAE,cAAc;SACL,CAAC,CAAC;IAC5B,CAAC;IACD,OAAO,MAAM,CAAC;AAClB,CAAC","sourcesContent":["import type {TAssistantMessage, TChatMessage, TMessageContentUnion} from '../../../types';\n\nimport {contentPartsToMessageContent} from './contentPartsToMessageContent';\n\nfunction isMessageContentEmpty(content: TAssistantMessage['content']): boolean {\n if (typeof content === 'string') return content.trim() === '';\n return Array.isArray(content) && content.length === 0;\n}\n\nexport type BuildFinalMessagesParams = {\n baseMessages: TChatMessage[];\n currentAssistantMessageId: string;\n contentParts: TMessageContentUnion[];\n};\n\nexport function buildFinalMessages(params: BuildFinalMessagesParams): TChatMessage[] {\n const {baseMessages, currentAssistantMessageId, contentParts} = params;\n const currentContent = contentPartsToMessageContent(contentParts);\n const result: TChatMessage[] = [...baseMessages];\n if (!isMessageContentEmpty(currentContent)) {\n result.push({\n id: currentAssistantMessageId,\n role: 'assistant',\n content: currentContent,\n } as TAssistantMessage);\n }\n return result;\n}\n"]}
|
|
@@ -2,11 +2,10 @@ import type { TAssistantMessage, TChatMessage } from "../../../types/index.js";
|
|
|
2
2
|
import type { OpenAIStreamEventLike } from "../types/index.js";
|
|
3
3
|
export type ConsumeStreamCallbacks = {
|
|
4
4
|
baseMessages: TChatMessage[];
|
|
5
|
-
getAssistantMessageId: (
|
|
5
|
+
getAssistantMessageId: () => string;
|
|
6
6
|
/** When the API sends `response.output_item.added` for a message, exposes OpenAI item id (e.g. msg-cc-…) for reactions. */
|
|
7
7
|
onAssistantMessageIdResolved?: (previousId: string, openaiItemId: string) => void;
|
|
8
8
|
onContentUpdate: (messageId: string, content: TAssistantMessage['content']) => void;
|
|
9
|
-
onNewMessage: (messageId: string) => void;
|
|
10
9
|
onEnd: (finalMessages: TChatMessage[], status: 'done' | 'error', error?: Error) => void;
|
|
11
10
|
getIsCancelled: () => boolean;
|
|
12
11
|
};
|
|
@@ -5,7 +5,6 @@ import { contentPartsToMessageContent } from "./contentPartsToMessageContent.js"
|
|
|
5
5
|
import { getOpenAIMessageItemIdFromOutputItemAdded } from "./getOpenAIMessageItemIdFromOutputItemAdded.js";
|
|
6
6
|
import { getStreamEndResult } from "./getStreamEndResult.js";
|
|
7
7
|
import { getStreamEventContentUpdate } from "./getStreamEventContentUpdate.js";
|
|
8
|
-
import { isMessageOutputItemDoneEvent } from "./isMessageOutputItemDoneEvent.js";
|
|
9
8
|
import { isStreamEndOrErrorEvent } from "./isStreamEndOrErrorEvent.js";
|
|
10
9
|
function waitForNextTask() {
|
|
11
10
|
return new Promise((resolve) => setTimeout(resolve, 0));
|
|
@@ -17,16 +16,27 @@ function shouldYieldAfterUpdate(update) {
|
|
|
17
16
|
/** Consumes stream, invokes callbacks. State/setters live in caller. */
|
|
18
17
|
export async function consumeOpenAIStream(stream, callbacks) {
|
|
19
18
|
var _a, e_1, _b, _c;
|
|
20
|
-
const { baseMessages, getAssistantMessageId, onAssistantMessageIdResolved, onContentUpdate,
|
|
21
|
-
|
|
22
|
-
let
|
|
23
|
-
let currentAssistantMessageId = getAssistantMessageId(0);
|
|
19
|
+
const { baseMessages, getAssistantMessageId, onAssistantMessageIdResolved, onContentUpdate, onEnd, getIsCancelled, } = callbacks;
|
|
20
|
+
let currentAssistantMessageId = getAssistantMessageId();
|
|
21
|
+
let assistantMessageItemIdResolved = false;
|
|
24
22
|
let contentParts = [];
|
|
25
23
|
const applyContentToCurrentMessage = (parts) => {
|
|
26
24
|
if (getIsCancelled())
|
|
27
25
|
return;
|
|
28
26
|
onContentUpdate(currentAssistantMessageId, contentPartsToMessageContent(parts));
|
|
29
27
|
};
|
|
28
|
+
const resolveAssistantMessageId = (openaiMessageItemId) => {
|
|
29
|
+
if (assistantMessageItemIdResolved)
|
|
30
|
+
return;
|
|
31
|
+
assistantMessageItemIdResolved = true;
|
|
32
|
+
if (openaiMessageItemId === currentAssistantMessageId)
|
|
33
|
+
return;
|
|
34
|
+
const previousId = currentAssistantMessageId;
|
|
35
|
+
currentAssistantMessageId = openaiMessageItemId;
|
|
36
|
+
if (!getIsCancelled()) {
|
|
37
|
+
onAssistantMessageIdResolved === null || onAssistantMessageIdResolved === void 0 ? void 0 : onAssistantMessageIdResolved(previousId, openaiMessageItemId);
|
|
38
|
+
}
|
|
39
|
+
};
|
|
30
40
|
try {
|
|
31
41
|
try {
|
|
32
42
|
for (var _d = true, stream_1 = __asyncValues(stream), stream_1_1; stream_1_1 = await stream_1.next(), _a = stream_1_1.done, !_a; _d = true) {
|
|
@@ -36,18 +46,13 @@ export async function consumeOpenAIStream(stream, callbacks) {
|
|
|
36
46
|
if (getIsCancelled())
|
|
37
47
|
return;
|
|
38
48
|
const openaiMessageItemId = getOpenAIMessageItemIdFromOutputItemAdded(event);
|
|
39
|
-
if (openaiMessageItemId
|
|
40
|
-
|
|
41
|
-
currentAssistantMessageId = openaiMessageItemId;
|
|
42
|
-
if (!getIsCancelled()) {
|
|
43
|
-
onAssistantMessageIdResolved === null || onAssistantMessageIdResolved === void 0 ? void 0 : onAssistantMessageIdResolved(previousId, openaiMessageItemId);
|
|
44
|
-
}
|
|
49
|
+
if (openaiMessageItemId) {
|
|
50
|
+
resolveAssistantMessageId(openaiMessageItemId);
|
|
45
51
|
continue;
|
|
46
52
|
}
|
|
47
53
|
if (isStreamEndOrErrorEvent(event)) {
|
|
48
54
|
const finalMessages = buildFinalMessages({
|
|
49
55
|
baseMessages,
|
|
50
|
-
completedAssistantMessages,
|
|
51
56
|
currentAssistantMessageId,
|
|
52
57
|
contentParts,
|
|
53
58
|
});
|
|
@@ -57,19 +62,6 @@ export async function consumeOpenAIStream(stream, callbacks) {
|
|
|
57
62
|
onEnd(finalMessages, endResult.status, endResult.status === 'error' ? endResult.error : undefined);
|
|
58
63
|
return;
|
|
59
64
|
}
|
|
60
|
-
if (isMessageOutputItemDoneEvent(event)) {
|
|
61
|
-
completedAssistantMessages.push({
|
|
62
|
-
id: currentAssistantMessageId,
|
|
63
|
-
content: contentPartsToMessageContent(contentParts),
|
|
64
|
-
});
|
|
65
|
-
messageIndex += 1;
|
|
66
|
-
currentAssistantMessageId = getAssistantMessageId(messageIndex);
|
|
67
|
-
contentParts = [];
|
|
68
|
-
if (getIsCancelled())
|
|
69
|
-
return;
|
|
70
|
-
onNewMessage(currentAssistantMessageId);
|
|
71
|
-
continue;
|
|
72
|
-
}
|
|
73
65
|
const update = getStreamEventContentUpdate(event);
|
|
74
66
|
if (!update)
|
|
75
67
|
continue;
|
|
@@ -95,7 +87,6 @@ export async function consumeOpenAIStream(stream, callbacks) {
|
|
|
95
87
|
if (!getIsCancelled()) {
|
|
96
88
|
const finalMessages = buildFinalMessages({
|
|
97
89
|
baseMessages,
|
|
98
|
-
completedAssistantMessages,
|
|
99
90
|
currentAssistantMessageId,
|
|
100
91
|
contentParts,
|
|
101
92
|
});
|
|
@@ -106,7 +97,6 @@ export async function consumeOpenAIStream(stream, callbacks) {
|
|
|
106
97
|
if (!getIsCancelled()) {
|
|
107
98
|
const finalMessages = buildFinalMessages({
|
|
108
99
|
baseMessages,
|
|
109
|
-
completedAssistantMessages,
|
|
110
100
|
currentAssistantMessageId,
|
|
111
101
|
contentParts,
|
|
112
102
|
});
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"consumeOpenAIStream.js","sourceRoot":"../../../../../src","sources":["adapters/openai/helpers/consumeOpenAIStream.ts"],"names":[],"mappings":";AAGA,OAAO,EAAC,kBAAkB,EAAC,gCAA6B;AACxD,OAAO,EAAC,kBAAkB,EAAC,gCAA6B;AACxD,OAAO,EAAC,4BAA4B,EAAC,0CAAuC;AAC5E,OAAO,EAAC,yCAAyC,EAAC,uDAAoD;AACtG,OAAO,EAAC,kBAAkB,EAAC,gCAA6B;AACxD,OAAO,EAAC,2BAA2B,EAAC,yCAAsC;AAC1E,OAAO,EAAC,
|
|
1
|
+
{"version":3,"file":"consumeOpenAIStream.js","sourceRoot":"../../../../../src","sources":["adapters/openai/helpers/consumeOpenAIStream.ts"],"names":[],"mappings":";AAGA,OAAO,EAAC,kBAAkB,EAAC,gCAA6B;AACxD,OAAO,EAAC,kBAAkB,EAAC,gCAA6B;AACxD,OAAO,EAAC,4BAA4B,EAAC,0CAAuC;AAC5E,OAAO,EAAC,yCAAyC,EAAC,uDAAoD;AACtG,OAAO,EAAC,kBAAkB,EAAC,gCAA6B;AACxD,OAAO,EAAC,2BAA2B,EAAC,yCAAsC;AAC1E,OAAO,EAAC,uBAAuB,EAAC,qCAAkC;AAElE,SAAS,eAAe;IACpB,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,UAAU,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC,CAAC;AAC5D,CAAC;AAED,SAAS,sBAAsB,CAAC,MAAsD;;IAClF,OAAO,CAAA,MAAM,aAAN,MAAM,uBAAN,MAAM,CAAE,IAAI,MAAK,UAAU,IAAI,CAAC,MAAA,MAAM,CAAC,MAAM,mCAAI,SAAS,CAAC,KAAK,SAAS,CAAC;AACrF,CAAC;AAYD,wEAAwE;AAExE,MAAM,CAAC,KAAK,UAAU,mBAAmB,CACrC,MAA4C,EAC5C,SAAiC;;IAEjC,MAAM,EACF,YAAY,EACZ,qBAAqB,EACrB,4BAA4B,EAC5B,eAAe,EACf,KAAK,EACL,cAAc,GACjB,GAAG,SAAS,CAAC;IAEd,IAAI,yBAAyB,GAAG,qBAAqB,EAAE,CAAC;IACxD,IAAI,8BAA8B,GAAG,KAAK,CAAC;IAC3C,IAAI,YAAY,GAA2B,EAAE,CAAC;IAE9C,MAAM,4BAA4B,GAAG,CAAC,KAA6B,EAAE,EAAE;QACnE,IAAI,cAAc,EAAE;YAAE,OAAO;QAC7B,eAAe,CAAC,yBAAyB,EAAE,4BAA4B,CAAC,KAAK,CAAC,CAAC,CAAC;IACpF,CAAC,CAAC;IAEF,MAAM,yBAAyB,GAAG,CAAC,mBAA2B,EAAE,EAAE;QAC9D,IAAI,8BAA8B;YAAE,OAAO;QAC3C,8BAA8B,GAAG,IAAI,CAAC;QACtC,IAAI,mBAAmB,KAAK,yBAAyB;YAAE,OAAO;QAE9D,MAAM,UAAU,GAAG,yBAAyB,CAAC;QAC7C,yBAAyB,GAAG,mBAAmB,CAAC;QAChD,IAAI,CAAC,cAAc,EAAE,EAAE,CAAC;YACpB,4BAA4B,aAA5B,4BAA4B,uBAA5B,4BAA4B,CAAG,UAAU,EAAE,mBAAmB,CAAC,CAAC;QACpE,CAAC;IACL,CAAC,CAAC;IAEF,IAAI,CAAC;;YACD,KAA0B,eAAA,WAAA,cAAA,MAAM,CAAA,YAAA,4EAAE,CAAC;gBAAT,sBAAM;gBAAN,WAAM;gBAArB,MAAM,KAAK,KAAA,CAAA;gBAClB,IAAI,cAAc,EAAE;oBAAE,OAAO;gBAE7B,MAAM,mBAAmB,GAAG,yCAAyC,CAAC,KAAK,CAAC,CAAC;gBAC7E,IAAI,mBAAmB,EAAE,CAAC;oBACtB,yBAAyB,CAAC,mBAAmB,CAAC,CAAC;oBAC/C,SAAS;gBACb,CAAC;gBAED,IAAI,uBAAuB,CAAC,KAAK,CAAC,EAAE,CAAC;oBACjC,MAAM,aAAa,GAAG,kBAAkB,CAAC;wBACrC,YAAY;wBACZ,yBAAyB;wBACzB,YAAY;qBACf,CAAC,CAAC;oBACH,MAAM,SAAS,GAAG,kBAAkB,CAAC,KAAK,CAAC,CAAC;oBAC5C,IAAI,cAAc,EAAE;wBAAE,OAAO;oBAC7B,KAAK,CACD,aAAa,EACb,SAAS,CAAC,MAAM,EAChB,SAAS,CAAC,MAAM,KAAK,OAAO,CAAC,CAAC,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC,CAAC,SAAS,CAC7D,CAAC;oBACF,OAAO;gBACX,CAAC;gBAED,MAAM,MAAM,GAAG,2BAA2B,CAAC,KAAK,CAAC,CAAC;gBAClD,IAAI,CAAC,MAAM;oBAAE,SAAS;gBAEtB,MAAM,SAAS,GAAG,kBAAkB,CAAC,YAAY,EAAE,MAAM,CAAC,CAAC;gBAC3D,IAAI,SAAS,KAAK,IAAI,EAAE,CAAC;oBACrB,YAAY,GAAG,SAAS,CAAC;oBACzB,4BAA4B,CAAC,YAAY,CAAC,CAAC;oBAC3C,IAAI,sBAAsB,CAAC,MAAM,CAAC,EAAE,CAAC;wBACjC,MAAM,eAAe,EAAE,CAAC;wBACxB,IAAI,cAAc,EAAE;4BAAE,OAAO;oBACjC,CAAC;gBACL,CAAC;YACL,CAAC;;;;;;;;;QAED,IAAI,CAAC,cAAc,EAAE,EAAE,CAAC;YACpB,MAAM,aAAa,GAAG,kBAAkB,CAAC;gBACrC,YAAY;gBACZ,yBAAyB;gBACzB,YAAY;aACf,CAAC,CAAC;YACH,KAAK,CAAC,aAAa,EAAE,MAAM,CAAC,CAAC;QACjC,CAAC;IACL,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACX,IAAI,CAAC,cAAc,EAAE,EAAE,CAAC;YACpB,MAAM,aAAa,GAAG,kBAAkB,CAAC;gBACrC,YAAY;gBACZ,yBAAyB;gBACzB,YAAY;aACf,CAAC,CAAC;YACH,KAAK,CAAC,aAAa,EAAE,OAAO,EAAE,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,KAAK,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC;QACvF,CAAC;IACL,CAAC;AACL,CAAC","sourcesContent":["import type {TAssistantMessage, TChatMessage, TMessageContentUnion} from '../../../types';\nimport type {OpenAIStreamEventLike} from '../types';\n\nimport {applyContentUpdate} from './applyContentUpdate';\nimport {buildFinalMessages} from './buildFinalMessages';\nimport {contentPartsToMessageContent} from './contentPartsToMessageContent';\nimport {getOpenAIMessageItemIdFromOutputItemAdded} from './getOpenAIMessageItemIdFromOutputItemAdded';\nimport {getStreamEndResult} from './getStreamEndResult';\nimport {getStreamEventContentUpdate} from './getStreamEventContentUpdate';\nimport {isStreamEndOrErrorEvent} from './isStreamEndOrErrorEvent';\n\nfunction waitForNextTask(): Promise<void> {\n return new Promise((resolve) => setTimeout(resolve, 0));\n}\n\nfunction shouldYieldAfterUpdate(update: ReturnType<typeof getStreamEventContentUpdate>): boolean {\n return update?.kind === 'tool_add' && (update.status ?? 'loading') === 'loading';\n}\n\nexport type ConsumeStreamCallbacks = {\n baseMessages: TChatMessage[];\n getAssistantMessageId: () => string;\n /** When the API sends `response.output_item.added` for a message, exposes OpenAI item id (e.g. msg-cc-…) for reactions. */\n onAssistantMessageIdResolved?: (previousId: string, openaiItemId: string) => void;\n onContentUpdate: (messageId: string, content: TAssistantMessage['content']) => void;\n onEnd: (finalMessages: TChatMessage[], status: 'done' | 'error', error?: Error) => void;\n getIsCancelled: () => boolean;\n};\n\n/** Consumes stream, invokes callbacks. State/setters live in caller. */\n\nexport async function consumeOpenAIStream(\n stream: AsyncIterable<OpenAIStreamEventLike>,\n callbacks: ConsumeStreamCallbacks,\n): Promise<void> {\n const {\n baseMessages,\n getAssistantMessageId,\n onAssistantMessageIdResolved,\n onContentUpdate,\n onEnd,\n getIsCancelled,\n } = callbacks;\n\n let currentAssistantMessageId = getAssistantMessageId();\n let assistantMessageItemIdResolved = false;\n let contentParts: TMessageContentUnion[] = [];\n\n const applyContentToCurrentMessage = (parts: TMessageContentUnion[]) => {\n if (getIsCancelled()) return;\n onContentUpdate(currentAssistantMessageId, contentPartsToMessageContent(parts));\n };\n\n const resolveAssistantMessageId = (openaiMessageItemId: string) => {\n if (assistantMessageItemIdResolved) return;\n assistantMessageItemIdResolved = true;\n if (openaiMessageItemId === currentAssistantMessageId) return;\n\n const previousId = currentAssistantMessageId;\n currentAssistantMessageId = openaiMessageItemId;\n if (!getIsCancelled()) {\n onAssistantMessageIdResolved?.(previousId, openaiMessageItemId);\n }\n };\n\n try {\n for await (const event of stream) {\n if (getIsCancelled()) return;\n\n const openaiMessageItemId = getOpenAIMessageItemIdFromOutputItemAdded(event);\n if (openaiMessageItemId) {\n resolveAssistantMessageId(openaiMessageItemId);\n continue;\n }\n\n if (isStreamEndOrErrorEvent(event)) {\n const finalMessages = buildFinalMessages({\n baseMessages,\n currentAssistantMessageId,\n contentParts,\n });\n const endResult = getStreamEndResult(event);\n if (getIsCancelled()) return;\n onEnd(\n finalMessages,\n endResult.status,\n endResult.status === 'error' ? endResult.error : undefined,\n );\n return;\n }\n\n const update = getStreamEventContentUpdate(event);\n if (!update) continue;\n\n const nextParts = applyContentUpdate(contentParts, update);\n if (nextParts !== null) {\n contentParts = nextParts;\n applyContentToCurrentMessage(contentParts);\n if (shouldYieldAfterUpdate(update)) {\n await waitForNextTask();\n if (getIsCancelled()) return;\n }\n }\n }\n\n if (!getIsCancelled()) {\n const finalMessages = buildFinalMessages({\n baseMessages,\n currentAssistantMessageId,\n contentParts,\n });\n onEnd(finalMessages, 'done');\n }\n } catch (err) {\n if (!getIsCancelled()) {\n const finalMessages = buildFinalMessages({\n baseMessages,\n currentAssistantMessageId,\n contentParts,\n });\n onEnd(finalMessages, 'error', err instanceof Error ? err : new Error(String(err)));\n }\n }\n}\n"]}
|
|
@@ -2,14 +2,5 @@ import type { TChatMessage } from "../../types/index.js";
|
|
|
2
2
|
import type { OpenAIStreamAdapterOptions, OpenAIStreamAdapterResult, OpenAIStreamSource } from "./types/index.js";
|
|
3
3
|
import { OpenAIResponseLike } from "./types/openAiTypes.js";
|
|
4
4
|
export type { OpenAIStreamAdapterOptions, OpenAIStreamAdapterResult, OpenAIStreamAdapterStatus, OpenAIStreamSource, } from "./types/index.js";
|
|
5
|
-
/**
|
|
6
|
-
* When the stream splits the reply into two consecutive assistant messages, the same tool call can
|
|
7
|
-
* appear in both. Moves matching tool parts into the earlier segment and drops the duplicate from
|
|
8
|
-
* the later one so the UI does not show orphaned repeats.
|
|
9
|
-
*
|
|
10
|
-
* @param messages - Chat messages (possibly with duplicate tool parts across adjacent assistants).
|
|
11
|
-
* @returns A shallow-copied message list with orphan duplicates collapsed.
|
|
12
|
-
*/
|
|
13
|
-
export declare function collapseOrphanToolBetweenAssistantSegments(messages: TChatMessage[]): TChatMessage[];
|
|
14
5
|
export declare function useOpenAIResponsesAdapter(response: OpenAIResponseLike | null): TChatMessage[];
|
|
15
6
|
export declare function useOpenAIStreamAdapter(stream: OpenAIStreamSource | null, options?: OpenAIStreamAdapterOptions): OpenAIStreamAdapterResult;
|
|
@@ -1,65 +1,13 @@
|
|
|
1
1
|
import { __asyncGenerator, __asyncValues, __await } from "tslib";
|
|
2
2
|
import { useEffect, useMemo, useRef, useState } from 'react';
|
|
3
3
|
import { consumeOpenAIStream } from "./helpers/consumeOpenAIStream.js";
|
|
4
|
-
import { contentPartsToMessageContent } from "./helpers/contentPartsToMessageContent.js";
|
|
5
4
|
import { fetchResponseToStreamEvents } from "./helpers/fetchResponseToStreamEvents.js";
|
|
6
5
|
import { isFetchResponse } from "./helpers/isFetchResponse.js";
|
|
7
6
|
import { openAIResponseToMessages } from "./helpers/openAIResponseToMessages.js";
|
|
8
7
|
/** Single non-streaming response → TChatMessage[]. For streaming use useOpenAIStreamAdapter. */
|
|
9
|
-
function isToolPart(p) {
|
|
10
|
-
return p.type === 'tool';
|
|
11
|
-
}
|
|
12
|
-
/**
|
|
13
|
-
* When the stream splits the reply into two consecutive assistant messages, the same tool call can
|
|
14
|
-
* appear in both. Moves matching tool parts into the earlier segment and drops the duplicate from
|
|
15
|
-
* the later one so the UI does not show orphaned repeats.
|
|
16
|
-
*
|
|
17
|
-
* @param messages - Chat messages (possibly with duplicate tool parts across adjacent assistants).
|
|
18
|
-
* @returns A shallow-copied message list with orphan duplicates collapsed.
|
|
19
|
-
*/
|
|
20
|
-
export function collapseOrphanToolBetweenAssistantSegments(messages) {
|
|
21
|
-
const result = messages.map((m) => m.role === 'assistant' ? Object.assign({}, m) : m);
|
|
22
|
-
for (let i = result.length - 1; i >= 1; i--) {
|
|
23
|
-
const next = result[i];
|
|
24
|
-
const prev = result[i - 1];
|
|
25
|
-
if (prev.role !== 'assistant' || next.role !== 'assistant') {
|
|
26
|
-
continue;
|
|
27
|
-
}
|
|
28
|
-
const nextContent = next.content;
|
|
29
|
-
if (!Array.isArray(nextContent)) {
|
|
30
|
-
continue;
|
|
31
|
-
}
|
|
32
|
-
const prevContent = prev.content;
|
|
33
|
-
if (!Array.isArray(prevContent)) {
|
|
34
|
-
continue;
|
|
35
|
-
}
|
|
36
|
-
const updatedNextParts = [...nextContent];
|
|
37
|
-
let updatedPrevParts = [...prevContent];
|
|
38
|
-
let changed = false;
|
|
39
|
-
for (let t = updatedNextParts.length - 1; t >= 0; t--) {
|
|
40
|
-
const part = updatedNextParts[t];
|
|
41
|
-
if (!isToolPart(part) || !part.id) {
|
|
42
|
-
continue;
|
|
43
|
-
}
|
|
44
|
-
const pIdx = updatedPrevParts.findIndex((p) => isToolPart(p) && p.id === part.id);
|
|
45
|
-
if (pIdx < 0) {
|
|
46
|
-
continue;
|
|
47
|
-
}
|
|
48
|
-
updatedPrevParts = updatedPrevParts.map((p, j) => (j === pIdx ? part : p));
|
|
49
|
-
updatedNextParts.splice(t, 1);
|
|
50
|
-
changed = true;
|
|
51
|
-
}
|
|
52
|
-
if (!changed) {
|
|
53
|
-
continue;
|
|
54
|
-
}
|
|
55
|
-
result[i - 1] = Object.assign(Object.assign({}, prev), { content: contentPartsToMessageContent(updatedPrevParts) });
|
|
56
|
-
result[i] = Object.assign(Object.assign({}, next), { content: contentPartsToMessageContent(updatedNextParts) });
|
|
57
|
-
}
|
|
58
|
-
return result;
|
|
59
|
-
}
|
|
60
8
|
/**
|
|
61
9
|
* Index of the assistant message that should receive streamed content for `messageId`.
|
|
62
|
-
* Uses an exact id match when present; otherwise the last assistant (current streaming
|
|
10
|
+
* Uses an exact id match when present; otherwise the last assistant (current streaming message),
|
|
63
11
|
* which covers events that reference an id before our list has caught up.
|
|
64
12
|
*
|
|
65
13
|
* @param messages - Current chat messages while streaming.
|
|
@@ -161,11 +109,11 @@ export function useOpenAIStreamAdapter(stream, options = {}) {
|
|
|
161
109
|
},
|
|
162
110
|
};
|
|
163
111
|
const baseMessages = initialMessagesRef.current;
|
|
164
|
-
const getAssistantMessageId = (
|
|
112
|
+
const getAssistantMessageId = () => assistantMessageId;
|
|
165
113
|
setMessages([
|
|
166
114
|
...baseMessages,
|
|
167
115
|
{
|
|
168
|
-
id: getAssistantMessageId(
|
|
116
|
+
id: getAssistantMessageId(),
|
|
169
117
|
role: 'assistant',
|
|
170
118
|
content: '',
|
|
171
119
|
},
|
|
@@ -187,12 +135,12 @@ export function useOpenAIStreamAdapter(stream, options = {}) {
|
|
|
187
135
|
onAssistantMessageIdResolved: (previousId, openaiItemId) => {
|
|
188
136
|
if (cancelled)
|
|
189
137
|
return;
|
|
190
|
-
setMessages((prev) =>
|
|
138
|
+
setMessages((prev) => prev.map((msg) => {
|
|
191
139
|
if (msg.id === previousId && msg.role === 'assistant') {
|
|
192
140
|
return Object.assign(Object.assign({}, msg), { id: openaiItemId });
|
|
193
141
|
}
|
|
194
142
|
return msg;
|
|
195
|
-
}))
|
|
143
|
+
}));
|
|
196
144
|
},
|
|
197
145
|
onContentUpdate: (messageId, content) => {
|
|
198
146
|
if (cancelled)
|
|
@@ -200,34 +148,21 @@ export function useOpenAIStreamAdapter(stream, options = {}) {
|
|
|
200
148
|
setMessages((prev) => {
|
|
201
149
|
const targetIndex = resolveStreamContentTargetIndex(prev, messageId);
|
|
202
150
|
if (targetIndex < 0) {
|
|
203
|
-
return
|
|
151
|
+
return prev;
|
|
204
152
|
}
|
|
205
|
-
|
|
153
|
+
return prev.map((msg, i) => {
|
|
206
154
|
if (i !== targetIndex) {
|
|
207
155
|
return msg;
|
|
208
156
|
}
|
|
209
157
|
return Object.assign(Object.assign({}, msg), { content });
|
|
210
158
|
});
|
|
211
|
-
return collapseOrphanToolBetweenAssistantSegments(mapped);
|
|
212
159
|
});
|
|
213
160
|
},
|
|
214
|
-
onNewMessage: (messageId) => {
|
|
215
|
-
if (cancelled)
|
|
216
|
-
return;
|
|
217
|
-
setMessages((prev) => [
|
|
218
|
-
...prev,
|
|
219
|
-
{
|
|
220
|
-
id: messageId,
|
|
221
|
-
role: 'assistant',
|
|
222
|
-
content: '',
|
|
223
|
-
},
|
|
224
|
-
]);
|
|
225
|
-
},
|
|
226
161
|
onEnd: (finalMessages, s, err) => {
|
|
227
162
|
var _a;
|
|
228
163
|
setStatus(s === 'done' ? 'ready' : s);
|
|
229
164
|
setError(err !== null && err !== void 0 ? err : null);
|
|
230
|
-
const normalized = injectUsageMetadata(
|
|
165
|
+
const normalized = injectUsageMetadata(finalMessages);
|
|
231
166
|
if (!cancelled) {
|
|
232
167
|
setMessages(normalized);
|
|
233
168
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useOpenAIResponsesAdapter.js","sourceRoot":"../../../../src","sources":["adapters/openai/useOpenAIResponsesAdapter.ts"],"names":[],"mappings":"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","sourcesContent":["import {useEffect, useMemo, useRef, useState} from 'react';\n\nimport type {TAssistantMessage, TChatMessage, TMessageContentUnion} from '../../types';\n\nimport {type ConsumeStreamCallbacks, consumeOpenAIStream} from './helpers/consumeOpenAIStream';\nimport {contentPartsToMessageContent} from './helpers/contentPartsToMessageContent';\nimport {fetchResponseToStreamEvents} from './helpers/fetchResponseToStreamEvents';\nimport {isFetchResponse} from './helpers/isFetchResponse';\nimport {openAIResponseToMessages} from './helpers/openAIResponseToMessages';\nimport type {\n OpenAIStreamAdapterOptions,\n OpenAIStreamAdapterResult,\n OpenAIStreamSource,\n} from './types';\nimport {OpenAIResponseLike, OpenAIStreamEventLike} from './types/openAiTypes';\n\nexport type {\n OpenAIStreamAdapterOptions,\n OpenAIStreamAdapterResult,\n OpenAIStreamAdapterStatus,\n OpenAIStreamSource,\n} from './types';\n\n/** Single non-streaming response → TChatMessage[]. For streaming use useOpenAIStreamAdapter. */\n\nfunction isToolPart(p: TMessageContentUnion): p is Extract<TMessageContentUnion, {type: 'tool'}> {\n return p.type === 'tool';\n}\n\n/**\n * When the stream splits the reply into two consecutive assistant messages, the same tool call can\n * appear in both. Moves matching tool parts into the earlier segment and drops the duplicate from\n * the later one so the UI does not show orphaned repeats.\n *\n * @param messages - Chat messages (possibly with duplicate tool parts across adjacent assistants).\n * @returns A shallow-copied message list with orphan duplicates collapsed.\n */\nexport function collapseOrphanToolBetweenAssistantSegments(\n messages: TChatMessage[],\n): TChatMessage[] {\n const result = messages.map((m) =>\n m.role === 'assistant' ? ({...m} as TAssistantMessage) : m,\n );\n\n for (let i = result.length - 1; i >= 1; i--) {\n const next = result[i] as TAssistantMessage;\n const prev = result[i - 1] as TAssistantMessage;\n if (prev.role !== 'assistant' || next.role !== 'assistant') {\n continue;\n }\n\n const nextContent = next.content;\n if (!Array.isArray(nextContent)) {\n continue;\n }\n\n const prevContent = prev.content;\n if (!Array.isArray(prevContent)) {\n continue;\n }\n\n const updatedNextParts = [...nextContent] as TMessageContentUnion[];\n let updatedPrevParts = [...prevContent] as TMessageContentUnion[];\n let changed = false;\n\n for (let t = updatedNextParts.length - 1; t >= 0; t--) {\n const part = updatedNextParts[t];\n if (!isToolPart(part) || !part.id) {\n continue;\n }\n const pIdx = updatedPrevParts.findIndex((p) => isToolPart(p) && p.id === part.id);\n if (pIdx < 0) {\n continue;\n }\n updatedPrevParts = updatedPrevParts.map((p, j) => (j === pIdx ? part : p));\n updatedNextParts.splice(t, 1);\n changed = true;\n }\n\n if (!changed) {\n continue;\n }\n\n result[i - 1] = {\n ...prev,\n content: contentPartsToMessageContent(updatedPrevParts),\n };\n result[i] = {\n ...next,\n content: contentPartsToMessageContent(updatedNextParts),\n };\n }\n\n return result;\n}\n\n/**\n * Index of the assistant message that should receive streamed content for `messageId`.\n * Uses an exact id match when present; otherwise the last assistant (current streaming segment),\n * which covers events that reference an id before our list has caught up.\n *\n * @param messages - Current chat messages while streaming.\n * @param messageId - Target assistant id from the stream event.\n * @returns Index of the assistant to update, or `-1` if there is no assistant.\n */\nfunction resolveStreamContentTargetIndex(messages: TChatMessage[], messageId: string): number {\n const byId = messages.findIndex((m) => m.id === messageId && m.role === 'assistant');\n if (byId >= 0) {\n return byId;\n }\n for (let i = messages.length - 1; i >= 0; i--) {\n if (messages[i].role === 'assistant') {\n return i;\n }\n }\n return -1;\n}\n\nfunction getResponseIdFromEvent(event: OpenAIStreamEventLike): string | null {\n const response = (event as {response?: unknown}).response;\n if (!response || typeof response !== 'object') {\n return null;\n }\n\n const id = (response as {id?: unknown}).id;\n return typeof id === 'string' ? id : null;\n}\n\nexport function useOpenAIResponsesAdapter(response: OpenAIResponseLike | null): TChatMessage[] {\n return useMemo(() => openAIResponseToMessages(response), [response]);\n}\n\nexport function useOpenAIStreamAdapter(\n stream: OpenAIStreamSource | null,\n options: OpenAIStreamAdapterOptions = {},\n): OpenAIStreamAdapterResult {\n const {\n initialMessages = [],\n assistantMessageId: optionId,\n onStreamEnd,\n trackTokenUsage = false,\n } = options;\n const onStreamEndRef = useRef(onStreamEnd);\n onStreamEndRef.current = onStreamEnd;\n const initialMessagesRef = useRef(initialMessages);\n initialMessagesRef.current = initialMessages;\n\n // messageId → output_tokens from response.completed event\n const usageMapRef = useRef<Map<string, number>>(new Map());\n\n const [messages, setMessages] = useState<TChatMessage[]>(initialMessages);\n const [status, setStatus] = useState<OpenAIStreamAdapterResult['status']>('ready');\n const [error, setError] = useState<Error | null>(null);\n const [responseId, setResponseId] = useState<string | null>(null);\n\n const assistantMessageId = useMemo(() => optionId ?? `assistant-${Date.now()}`, [optionId]);\n\n useEffect(() => {\n if (!stream) {\n setStatus('ready');\n setError(null);\n setResponseId(null);\n return undefined;\n }\n\n const sourceStream: AsyncIterable<OpenAIStreamEventLike> = isFetchResponse(stream)\n ? fetchResponseToStreamEvents(stream)\n : stream;\n\n let cancelled = false;\n setStatus('streaming');\n setError(null);\n setResponseId(null);\n usageMapRef.current = new Map();\n\n const streamToConsume: AsyncIterable<OpenAIStreamEventLike> = {\n async *[Symbol.asyncIterator]() {\n for await (const event of sourceStream) {\n const nextResponseId = getResponseIdFromEvent(event);\n if (!cancelled && nextResponseId) {\n setResponseId((prev) => (prev === nextResponseId ? prev : nextResponseId));\n }\n\n // Extract token usage from response.completed (only when opt-in)\n if (trackTokenUsage) {\n const e = event as Record<string, unknown>;\n const eventType =\n (e.type as string | undefined) ?? (e.event as string | undefined);\n if (eventType === 'response.completed' && e.response) {\n const response = e.response as {\n usage?: {output_tokens?: number};\n output?: Array<{id?: string; type?: string}>;\n };\n const tokens = response.usage?.output_tokens ?? 0;\n if (tokens > 0) {\n (response.output ?? []).forEach(({id, type}) => {\n if (id && type === 'message') {\n usageMapRef.current.set(id, tokens);\n }\n });\n }\n }\n }\n\n yield event;\n }\n },\n };\n\n const baseMessages = initialMessagesRef.current;\n const getAssistantMessageId = (index: number) =>\n index === 0 ? assistantMessageId : `${assistantMessageId}-${index}`;\n\n setMessages([\n ...baseMessages,\n {\n id: getAssistantMessageId(0),\n role: 'assistant',\n content: '',\n } as TAssistantMessage,\n ]);\n\n const injectUsageMetadata = (msgs: TChatMessage[]): TChatMessage[] => {\n if (usageMapRef.current.size === 0) return msgs;\n return msgs.map((msg) => {\n if (msg.role === 'assistant' && msg.id && usageMapRef.current.has(msg.id)) {\n return {\n ...msg,\n metadata: {\n ...msg.metadata,\n outputTokens: usageMapRef.current.get(msg.id),\n },\n } as TAssistantMessage;\n }\n return msg;\n });\n };\n\n const callbacks: ConsumeStreamCallbacks = {\n baseMessages,\n getAssistantMessageId,\n // Provisional id from getAssistantMessageId → OpenAI item id once the stream provides it.\n onAssistantMessageIdResolved: (previousId, openaiItemId) => {\n if (cancelled) return;\n setMessages((prev) =>\n collapseOrphanToolBetweenAssistantSegments(\n prev.map((msg): TChatMessage => {\n if (msg.id === previousId && msg.role === 'assistant') {\n return {...msg, id: openaiItemId} as TAssistantMessage;\n }\n return msg;\n }),\n ),\n );\n },\n onContentUpdate: (messageId, content) => {\n if (cancelled) return;\n setMessages((prev) => {\n const targetIndex = resolveStreamContentTargetIndex(prev, messageId);\n if (targetIndex < 0) {\n return collapseOrphanToolBetweenAssistantSegments(prev);\n }\n const mapped = prev.map((msg, i) => {\n if (i !== targetIndex) {\n return msg;\n }\n return {...msg, content} as TAssistantMessage;\n });\n return collapseOrphanToolBetweenAssistantSegments(mapped);\n });\n },\n onNewMessage: (messageId) => {\n if (cancelled) return;\n setMessages((prev) => [\n ...prev,\n {\n id: messageId,\n role: 'assistant',\n content: '',\n } as TAssistantMessage,\n ]);\n },\n onEnd: (finalMessages, s, err) => {\n setStatus(s === 'done' ? 'ready' : s);\n setError(err ?? null);\n const normalized = injectUsageMetadata(\n collapseOrphanToolBetweenAssistantSegments(finalMessages),\n );\n if (!cancelled) {\n setMessages(normalized);\n }\n onStreamEndRef.current?.(normalized);\n },\n getIsCancelled: () => cancelled,\n };\n\n consumeOpenAIStream(streamToConsume, callbacks);\n\n return () => {\n cancelled = true;\n };\n }, [stream, assistantMessageId]);\n\n return {messages, status, error, responseId};\n}\n"]}
|
|
1
|
+
{"version":3,"file":"useOpenAIResponsesAdapter.js","sourceRoot":"../../../../src","sources":["adapters/openai/useOpenAIResponsesAdapter.ts"],"names":[],"mappings":"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","sourcesContent":["import {useEffect, useMemo, useRef, useState} from 'react';\n\nimport type {TAssistantMessage, TChatMessage} from '../../types';\n\nimport {type ConsumeStreamCallbacks, consumeOpenAIStream} from './helpers/consumeOpenAIStream';\nimport {fetchResponseToStreamEvents} from './helpers/fetchResponseToStreamEvents';\nimport {isFetchResponse} from './helpers/isFetchResponse';\nimport {openAIResponseToMessages} from './helpers/openAIResponseToMessages';\nimport type {\n OpenAIStreamAdapterOptions,\n OpenAIStreamAdapterResult,\n OpenAIStreamSource,\n} from './types';\nimport {OpenAIResponseLike, OpenAIStreamEventLike} from './types/openAiTypes';\n\nexport type {\n OpenAIStreamAdapterOptions,\n OpenAIStreamAdapterResult,\n OpenAIStreamAdapterStatus,\n OpenAIStreamSource,\n} from './types';\n\n/** Single non-streaming response → TChatMessage[]. For streaming use useOpenAIStreamAdapter. */\n\n/**\n * Index of the assistant message that should receive streamed content for `messageId`.\n * Uses an exact id match when present; otherwise the last assistant (current streaming message),\n * which covers events that reference an id before our list has caught up.\n *\n * @param messages - Current chat messages while streaming.\n * @param messageId - Target assistant id from the stream event.\n * @returns Index of the assistant to update, or `-1` if there is no assistant.\n */\nfunction resolveStreamContentTargetIndex(messages: TChatMessage[], messageId: string): number {\n const byId = messages.findIndex((m) => m.id === messageId && m.role === 'assistant');\n if (byId >= 0) {\n return byId;\n }\n for (let i = messages.length - 1; i >= 0; i--) {\n if (messages[i].role === 'assistant') {\n return i;\n }\n }\n return -1;\n}\n\nfunction getResponseIdFromEvent(event: OpenAIStreamEventLike): string | null {\n const response = (event as {response?: unknown}).response;\n if (!response || typeof response !== 'object') {\n return null;\n }\n\n const id = (response as {id?: unknown}).id;\n return typeof id === 'string' ? id : null;\n}\n\nexport function useOpenAIResponsesAdapter(response: OpenAIResponseLike | null): TChatMessage[] {\n return useMemo(() => openAIResponseToMessages(response), [response]);\n}\n\nexport function useOpenAIStreamAdapter(\n stream: OpenAIStreamSource | null,\n options: OpenAIStreamAdapterOptions = {},\n): OpenAIStreamAdapterResult {\n const {\n initialMessages = [],\n assistantMessageId: optionId,\n onStreamEnd,\n trackTokenUsage = false,\n } = options;\n const onStreamEndRef = useRef(onStreamEnd);\n onStreamEndRef.current = onStreamEnd;\n const initialMessagesRef = useRef(initialMessages);\n initialMessagesRef.current = initialMessages;\n\n // messageId → output_tokens from response.completed event\n const usageMapRef = useRef<Map<string, number>>(new Map());\n\n const [messages, setMessages] = useState<TChatMessage[]>(initialMessages);\n const [status, setStatus] = useState<OpenAIStreamAdapterResult['status']>('ready');\n const [error, setError] = useState<Error | null>(null);\n const [responseId, setResponseId] = useState<string | null>(null);\n\n const assistantMessageId = useMemo(() => optionId ?? `assistant-${Date.now()}`, [optionId]);\n\n useEffect(() => {\n if (!stream) {\n setStatus('ready');\n setError(null);\n setResponseId(null);\n return undefined;\n }\n\n const sourceStream: AsyncIterable<OpenAIStreamEventLike> = isFetchResponse(stream)\n ? fetchResponseToStreamEvents(stream)\n : stream;\n\n let cancelled = false;\n setStatus('streaming');\n setError(null);\n setResponseId(null);\n usageMapRef.current = new Map();\n\n const streamToConsume: AsyncIterable<OpenAIStreamEventLike> = {\n async *[Symbol.asyncIterator]() {\n for await (const event of sourceStream) {\n const nextResponseId = getResponseIdFromEvent(event);\n if (!cancelled && nextResponseId) {\n setResponseId((prev) => (prev === nextResponseId ? prev : nextResponseId));\n }\n\n // Extract token usage from response.completed (only when opt-in)\n if (trackTokenUsage) {\n const e = event as Record<string, unknown>;\n const eventType =\n (e.type as string | undefined) ?? (e.event as string | undefined);\n if (eventType === 'response.completed' && e.response) {\n const response = e.response as {\n usage?: {output_tokens?: number};\n output?: Array<{id?: string; type?: string}>;\n };\n const tokens = response.usage?.output_tokens ?? 0;\n if (tokens > 0) {\n (response.output ?? []).forEach(({id, type}) => {\n if (id && type === 'message') {\n usageMapRef.current.set(id, tokens);\n }\n });\n }\n }\n }\n\n yield event;\n }\n },\n };\n\n const baseMessages = initialMessagesRef.current;\n const getAssistantMessageId = () => assistantMessageId;\n\n setMessages([\n ...baseMessages,\n {\n id: getAssistantMessageId(),\n role: 'assistant',\n content: '',\n } as TAssistantMessage,\n ]);\n\n const injectUsageMetadata = (msgs: TChatMessage[]): TChatMessage[] => {\n if (usageMapRef.current.size === 0) return msgs;\n return msgs.map((msg) => {\n if (msg.role === 'assistant' && msg.id && usageMapRef.current.has(msg.id)) {\n return {\n ...msg,\n metadata: {\n ...msg.metadata,\n outputTokens: usageMapRef.current.get(msg.id),\n },\n } as TAssistantMessage;\n }\n return msg;\n });\n };\n\n const callbacks: ConsumeStreamCallbacks = {\n baseMessages,\n getAssistantMessageId,\n // Provisional id from getAssistantMessageId → OpenAI item id once the stream provides it.\n onAssistantMessageIdResolved: (previousId, openaiItemId) => {\n if (cancelled) return;\n setMessages((prev) =>\n prev.map((msg): TChatMessage => {\n if (msg.id === previousId && msg.role === 'assistant') {\n return {...msg, id: openaiItemId} as TAssistantMessage;\n }\n return msg;\n }),\n );\n },\n onContentUpdate: (messageId, content) => {\n if (cancelled) return;\n setMessages((prev) => {\n const targetIndex = resolveStreamContentTargetIndex(prev, messageId);\n if (targetIndex < 0) {\n return prev;\n }\n return prev.map((msg, i) => {\n if (i !== targetIndex) {\n return msg;\n }\n return {...msg, content} as TAssistantMessage;\n });\n });\n },\n onEnd: (finalMessages, s, err) => {\n setStatus(s === 'done' ? 'ready' : s);\n setError(err ?? null);\n const normalized = injectUsageMetadata(finalMessages);\n if (!cancelled) {\n setMessages(normalized);\n }\n onStreamEndRef.current?.(normalized);\n },\n getIsCancelled: () => cancelled,\n };\n\n consumeOpenAIStream(streamToConsume, callbacks);\n\n return () => {\n cancelled = true;\n };\n }, [stream, assistantMessageId]);\n\n return {messages, status, error, responseId};\n}\n"]}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export type ControlSize = 'xs' | 's' | 'm' | 'l' | 'xl';
|
|
1
|
+
export type ControlSize = 'xxs' | 'xs' | 's' | 'm' | 'l' | 'xl';
|
|
2
2
|
/**
|
|
3
3
|
* Resolves a control size: an explicit size always wins, otherwise the mobile
|
|
4
4
|
* default is used in mobile mode and the desktop default elsewhere.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useMobileControlSize.js","sourceRoot":"../../../src","sources":["hooks/useMobileControlSize.ts"],"names":[],"mappings":"AAAA,OAAO,EAAC,SAAS,EAAC,MAAM,mBAAmB,CAAC;
|
|
1
|
+
{"version":3,"file":"useMobileControlSize.js","sourceRoot":"../../../src","sources":["hooks/useMobileControlSize.ts"],"names":[],"mappings":"AAAA,OAAO,EAAC,SAAS,EAAC,MAAM,mBAAmB,CAAC;AAM5C;;;GAGG;AACH,MAAM,UAAU,wBAAwB,CAAwB,MAK/D;IACG,MAAM,EAAC,IAAI,EAAE,cAAc,EAAE,aAAa,EAAE,QAAQ,EAAC,GAAG,MAAM,CAAC;IAE/D,OAAO,IAAI,aAAJ,IAAI,cAAJ,IAAI,GAAI,CAAC,QAAQ,CAAC,CAAC,CAAC,aAAa,CAAC,CAAC,CAAC,cAAc,CAAC,CAAC;AAC/D,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,oBAAoB,CAChC,IAAmB,EACnB,cAAiB,EACjB,aAAgB;IAEhB,MAAM,QAAQ,GAAG,SAAS,EAAE,CAAC;IAE7B,OAAO,wBAAwB,CAAC,EAAC,IAAI,EAAE,cAAc,EAAE,aAAa,EAAE,QAAQ,EAAC,CAAC,CAAC;AACrF,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,kBAAkB,CAAC,IAA6B;IAC5D,OAAO,IAAI,KAAK,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;AACnC,CAAC","sourcesContent":["import {useMobile} from '@gravity-ui/uikit';\n\n// `xxs` only exists on `Label`, which uikit 7.48 gained. It is listed here so that the helpers\n// stay usable with the whole `LabelProps['size']` union across the supported uikit range.\nexport type ControlSize = 'xxs' | 'xs' | 's' | 'm' | 'l' | 'xl';\n\n/**\n * Resolves a control size: an explicit size always wins, otherwise the mobile\n * default is used in mobile mode and the desktop default elsewhere.\n */\nexport function resolveMobileControlSize<T extends ControlSize>(params: {\n size: T | undefined;\n desktopDefault: T;\n mobileDefault: T;\n isMobile: boolean;\n}): T {\n const {size, desktopDefault, mobileDefault, isMobile} = params;\n\n return size ?? (isMobile ? mobileDefault : desktopDefault);\n}\n\n/**\n * Mobile-aware control size, driven by the uikit `MobileProvider` context.\n */\nexport function useMobileControlSize<T extends ControlSize>(\n size: T | undefined,\n desktopDefault: T,\n mobileDefault: T,\n): T {\n const isMobile = useMobile();\n\n return resolveMobileControlSize({size, desktopDefault, mobileDefault, isMobile});\n}\n\n/**\n * Icon size matching the resolved control size.\n */\nexport function getControlIconSize(size: ControlSize | undefined): number {\n return size === 'xl' ? 20 : 16;\n}\n"]}
|
package/build/esm/package.json
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":"2.20.
|
|
1
|
+
{"version":"2.20.2-beta.1b9a51bfdf2818e8caefe25d7de3af232af29ca8.0","type":"module","sideEffects":["*.css","*.scss"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@gravity-ui/aikit",
|
|
3
|
-
"version": "2.20.
|
|
3
|
+
"version": "2.20.2-beta.1b9a51bfdf2818e8caefe25d7de3af232af29ca8.0",
|
|
4
4
|
"description": "Gravity UI base kit for building ai assistant chats",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "commonjs",
|
|
@@ -745,7 +745,7 @@
|
|
|
745
745
|
"@gravity-ui/readme-validator": "^1.3.0",
|
|
746
746
|
"@gravity-ui/stylelint-config": "^4.0.1",
|
|
747
747
|
"@gravity-ui/tsconfig": "^1.0.0",
|
|
748
|
-
"@gravity-ui/uikit": "^7.
|
|
748
|
+
"@gravity-ui/uikit": "^7.48.3",
|
|
749
749
|
"@playwright/experimental-ct-react": "^1.56.1",
|
|
750
750
|
"@playwright/test": "^1.56.1",
|
|
751
751
|
"@storybook/addon-docs": "^9.0.5",
|
|
@@ -1,3 +0,0 @@
|
|
|
1
|
-
import { OpenAIStreamEventLike } from "../types/index.js";
|
|
2
|
-
/** output_item.done for item.type === 'message' only; other types (MCP, tool, reasoning) return false. */
|
|
3
|
-
export declare function isMessageOutputItemDoneEvent(event: OpenAIStreamEventLike | null | undefined): boolean;
|
|
@@ -1,22 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.isMessageOutputItemDoneEvent = isMessageOutputItemDoneEvent;
|
|
4
|
-
const isOutputItemDoneEvent_1 = require("./isOutputItemDoneEvent.js");
|
|
5
|
-
/** output_item.done for item.type === 'message' only; other types (MCP, tool, reasoning) return false. */
|
|
6
|
-
function isMessageOutputItemDoneEvent(event) {
|
|
7
|
-
var _a, _b, _c;
|
|
8
|
-
if (!(0, isOutputItemDoneEvent_1.isOutputItemDoneEvent)(event)) {
|
|
9
|
-
return false;
|
|
10
|
-
}
|
|
11
|
-
const e = event;
|
|
12
|
-
const item = (_a = e.item) !== null && _a !== void 0 ? _a : (_b = e.data) === null || _b === void 0 ? void 0 : _b.item;
|
|
13
|
-
if (item === undefined) {
|
|
14
|
-
if (typeof process !== 'undefined' && ((_c = process.env) === null || _c === void 0 ? void 0 : _c.NODE_ENV) !== 'production') {
|
|
15
|
-
/* eslint-disable-next-line no-console */
|
|
16
|
-
console.warn('[useOpenAIStreamAdapter] output_item.done event has no item; treating as message done.', event);
|
|
17
|
-
}
|
|
18
|
-
return true;
|
|
19
|
-
}
|
|
20
|
-
return item.type === 'message';
|
|
21
|
-
}
|
|
22
|
-
//# sourceMappingURL=isMessageOutputItemDoneEvent.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"isMessageOutputItemDoneEvent.js","sourceRoot":"../../../../../src","sources":["adapters/openai/helpers/isMessageOutputItemDoneEvent.ts"],"names":[],"mappings":";;AAMA,oEAqBC;AAzBD,sEAA8D;AAE9D,0GAA0G;AAE1G,SAAgB,4BAA4B,CACxC,KAA+C;;IAE/C,IAAI,CAAC,IAAA,6CAAqB,EAAC,KAAK,CAAC,EAAE,CAAC;QAChC,OAAO,KAAK,CAAC;IACjB,CAAC;IACD,MAAM,CAAC,GAAG,KAAgC,CAAC;IAC3C,MAAM,IAAI,GACN,MAAC,CAAC,CAAC,IAAoC,mCACvC,MAAC,CAAC,CAAC,IAA6C,0CAAE,IAAI,CAAC;IAC3D,IAAI,IAAI,KAAK,SAAS,EAAE,CAAC;QACrB,IAAI,OAAO,OAAO,KAAK,WAAW,IAAI,CAAA,MAAA,OAAO,CAAC,GAAG,0CAAE,QAAQ,MAAK,YAAY,EAAE,CAAC;YAC3E,yCAAyC;YACzC,OAAO,CAAC,IAAI,CACR,wFAAwF,EACxF,KAAK,CACR,CAAC;QACN,CAAC;QACD,OAAO,IAAI,CAAC;IAChB,CAAC;IACD,OAAO,IAAI,CAAC,IAAI,KAAK,SAAS,CAAC;AACnC,CAAC","sourcesContent":["import {OpenAIStreamEventLike} from '../types';\n\nimport {isOutputItemDoneEvent} from './isOutputItemDoneEvent';\n\n/** output_item.done for item.type === 'message' only; other types (MCP, tool, reasoning) return false. */\n\nexport function isMessageOutputItemDoneEvent(\n event: OpenAIStreamEventLike | null | undefined,\n): boolean {\n if (!isOutputItemDoneEvent(event)) {\n return false;\n }\n const e = event as Record<string, unknown>;\n const item =\n (e.item as {type?: string} | undefined) ??\n (e.data as {item?: {type?: string}} | undefined)?.item;\n if (item === undefined) {\n if (typeof process !== 'undefined' && process.env?.NODE_ENV !== 'production') {\n /* eslint-disable-next-line no-console */\n console.warn(\n '[useOpenAIStreamAdapter] output_item.done event has no item; treating as message done.',\n event,\n );\n }\n return true;\n }\n return item.type === 'message';\n}\n"]}
|
|
@@ -1,3 +0,0 @@
|
|
|
1
|
-
import { OpenAIStreamEventLike } from "../types/index.js";
|
|
2
|
-
/** output_item.done for item.type === 'message' only; other types (MCP, tool, reasoning) return false. */
|
|
3
|
-
export declare function isMessageOutputItemDoneEvent(event: OpenAIStreamEventLike | null | undefined): boolean;
|
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
import { isOutputItemDoneEvent } from "./isOutputItemDoneEvent.js";
|
|
2
|
-
/** output_item.done for item.type === 'message' only; other types (MCP, tool, reasoning) return false. */
|
|
3
|
-
export function isMessageOutputItemDoneEvent(event) {
|
|
4
|
-
var _a, _b, _c;
|
|
5
|
-
if (!isOutputItemDoneEvent(event)) {
|
|
6
|
-
return false;
|
|
7
|
-
}
|
|
8
|
-
const e = event;
|
|
9
|
-
const item = (_a = e.item) !== null && _a !== void 0 ? _a : (_b = e.data) === null || _b === void 0 ? void 0 : _b.item;
|
|
10
|
-
if (item === undefined) {
|
|
11
|
-
if (typeof process !== 'undefined' && ((_c = process.env) === null || _c === void 0 ? void 0 : _c.NODE_ENV) !== 'production') {
|
|
12
|
-
/* eslint-disable-next-line no-console */
|
|
13
|
-
console.warn('[useOpenAIStreamAdapter] output_item.done event has no item; treating as message done.', event);
|
|
14
|
-
}
|
|
15
|
-
return true;
|
|
16
|
-
}
|
|
17
|
-
return item.type === 'message';
|
|
18
|
-
}
|
|
19
|
-
//# sourceMappingURL=isMessageOutputItemDoneEvent.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"isMessageOutputItemDoneEvent.js","sourceRoot":"../../../../../src","sources":["adapters/openai/helpers/isMessageOutputItemDoneEvent.ts"],"names":[],"mappings":"AAEA,OAAO,EAAC,qBAAqB,EAAC,mCAAgC;AAE9D,0GAA0G;AAE1G,MAAM,UAAU,4BAA4B,CACxC,KAA+C;;IAE/C,IAAI,CAAC,qBAAqB,CAAC,KAAK,CAAC,EAAE,CAAC;QAChC,OAAO,KAAK,CAAC;IACjB,CAAC;IACD,MAAM,CAAC,GAAG,KAAgC,CAAC;IAC3C,MAAM,IAAI,GACN,MAAC,CAAC,CAAC,IAAoC,mCACvC,MAAC,CAAC,CAAC,IAA6C,0CAAE,IAAI,CAAC;IAC3D,IAAI,IAAI,KAAK,SAAS,EAAE,CAAC;QACrB,IAAI,OAAO,OAAO,KAAK,WAAW,IAAI,CAAA,MAAA,OAAO,CAAC,GAAG,0CAAE,QAAQ,MAAK,YAAY,EAAE,CAAC;YAC3E,yCAAyC;YACzC,OAAO,CAAC,IAAI,CACR,wFAAwF,EACxF,KAAK,CACR,CAAC;QACN,CAAC;QACD,OAAO,IAAI,CAAC;IAChB,CAAC;IACD,OAAO,IAAI,CAAC,IAAI,KAAK,SAAS,CAAC;AACnC,CAAC","sourcesContent":["import {OpenAIStreamEventLike} from '../types';\n\nimport {isOutputItemDoneEvent} from './isOutputItemDoneEvent';\n\n/** output_item.done for item.type === 'message' only; other types (MCP, tool, reasoning) return false. */\n\nexport function isMessageOutputItemDoneEvent(\n event: OpenAIStreamEventLike | null | undefined,\n): boolean {\n if (!isOutputItemDoneEvent(event)) {\n return false;\n }\n const e = event as Record<string, unknown>;\n const item =\n (e.item as {type?: string} | undefined) ??\n (e.data as {item?: {type?: string}} | undefined)?.item;\n if (item === undefined) {\n if (typeof process !== 'undefined' && process.env?.NODE_ENV !== 'production') {\n /* eslint-disable-next-line no-console */\n console.warn(\n '[useOpenAIStreamAdapter] output_item.done event has no item; treating as message done.',\n event,\n );\n }\n return true;\n }\n return item.type === 'message';\n}\n"]}
|