@omni-commerce/sdk 0.7.0 → 0.8.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/README.md +2 -0
- package/dist/embed.cjs +102 -2
- package/dist/embed.cjs.map +1 -1
- package/dist/embed.d.cts +23 -4
- package/dist/embed.d.ts +23 -4
- package/dist/embed.global.js +7 -7
- package/dist/embed.global.js.map +1 -1
- package/dist/embed.js +102 -2
- package/dist/embed.js.map +1 -1
- package/dist/index.cjs +1 -1
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/react.cjs +120 -7
- package/dist/react.cjs.map +1 -1
- package/dist/react.d.cts +1 -1
- package/dist/react.d.ts +1 -1
- package/dist/react.js +120 -7
- package/dist/react.js.map +1 -1
- package/package.json +1 -1
package/dist/react.cjs
CHANGED
|
@@ -27,6 +27,89 @@ __export(react_exports, {
|
|
|
27
27
|
module.exports = __toCommonJS(react_exports);
|
|
28
28
|
var import_react = require("react");
|
|
29
29
|
|
|
30
|
+
// src/application.ts
|
|
31
|
+
function boundedJson(value, maximum) {
|
|
32
|
+
const encoded = JSON.stringify(value);
|
|
33
|
+
if (typeof encoded !== "string" || new TextEncoder().encode(encoded).length > maximum || /data:[^"\s]{0,100};base64,/i.test(encoded))
|
|
34
|
+
throw new Error(
|
|
35
|
+
"Application data must be bounded JSON without base64 payloads."
|
|
36
|
+
);
|
|
37
|
+
return JSON.parse(encoded);
|
|
38
|
+
}
|
|
39
|
+
function createApplicationMessageHandler(input) {
|
|
40
|
+
const calls = /* @__PURE__ */ new Map();
|
|
41
|
+
return async (message) => {
|
|
42
|
+
if (typeof message.requestId !== "string" || message.requestId.length > 200)
|
|
43
|
+
return;
|
|
44
|
+
try {
|
|
45
|
+
if (message.type === "omni-chat:application-request") {
|
|
46
|
+
const application = input.options.application;
|
|
47
|
+
input.reply({
|
|
48
|
+
type: "omni-chat:application-response",
|
|
49
|
+
requestId: message.requestId,
|
|
50
|
+
application: application ? boundedJson(
|
|
51
|
+
{
|
|
52
|
+
name: application.name,
|
|
53
|
+
description: application.description,
|
|
54
|
+
context: await application.getContext() ?? null,
|
|
55
|
+
tools: Object.entries(input.options.frontendTools ?? {}).map(
|
|
56
|
+
([name, definition]) => ({
|
|
57
|
+
name,
|
|
58
|
+
description: definition.description,
|
|
59
|
+
parameters: definition.parameters
|
|
60
|
+
})
|
|
61
|
+
)
|
|
62
|
+
},
|
|
63
|
+
65536
|
|
64
|
+
) : void 0
|
|
65
|
+
});
|
|
66
|
+
} else if (message.type === "omni-chat:tool-request") {
|
|
67
|
+
const name = message.toolName;
|
|
68
|
+
const toolCallId = message.toolCallId;
|
|
69
|
+
if (typeof name !== "string" || !/^app_[a-z][a-z0-9_]{0,59}$/.test(name) || typeof toolCallId !== "string" || !toolCallId || toolCallId.length > 200)
|
|
70
|
+
throw new Error("Invalid application tool request.");
|
|
71
|
+
const definition = Object.hasOwn(
|
|
72
|
+
input.options.frontendTools ?? {},
|
|
73
|
+
name
|
|
74
|
+
) ? input.options.frontendTools?.[name] : void 0;
|
|
75
|
+
if (!input.options.application || !definition)
|
|
76
|
+
throw new Error("This application action is unavailable.");
|
|
77
|
+
const args = message.args;
|
|
78
|
+
if (!args || typeof args !== "object" || Array.isArray(args))
|
|
79
|
+
throw new Error("Invalid application action arguments.");
|
|
80
|
+
const signature = JSON.stringify({ name, args });
|
|
81
|
+
const prior = calls.get(toolCallId);
|
|
82
|
+
if (prior && prior.signature !== signature)
|
|
83
|
+
throw new Error("This tool call ID was already used.");
|
|
84
|
+
const result = prior?.result ?? Promise.resolve().then(
|
|
85
|
+
() => definition.execute(args, {
|
|
86
|
+
toolCallId,
|
|
87
|
+
abortSignal: input.signal
|
|
88
|
+
})
|
|
89
|
+
);
|
|
90
|
+
if (!prior) {
|
|
91
|
+
if (calls.size >= 200)
|
|
92
|
+
throw new Error("Start a new application chat.");
|
|
93
|
+
calls.set(toolCallId, { signature, result });
|
|
94
|
+
}
|
|
95
|
+
input.reply({
|
|
96
|
+
type: "omni-chat:tool-response",
|
|
97
|
+
requestId: message.requestId,
|
|
98
|
+
toolCallId,
|
|
99
|
+
result: boundedJson(await result ?? null, 12e3)
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
} catch (error) {
|
|
103
|
+
input.reply({
|
|
104
|
+
type: message.type === "omni-chat:application-request" ? "omni-chat:application-response" : "omni-chat:tool-response",
|
|
105
|
+
requestId: message.requestId,
|
|
106
|
+
toolCallId: message.toolCallId,
|
|
107
|
+
error: error instanceof Error ? error.message : "The application action failed."
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
|
|
30
113
|
// src/embed.ts
|
|
31
114
|
function getOmniChatEmbedUrl(options, parentOrigin) {
|
|
32
115
|
if (!/^[\da-f]{8}-[\da-f]{4}-[\da-f]{4}-[\da-f]{4}-[\da-f]{12}$/i.test(
|
|
@@ -88,9 +171,24 @@ function createOmniChatEmbed(container, options) {
|
|
|
88
171
|
}
|
|
89
172
|
return result;
|
|
90
173
|
};
|
|
174
|
+
const applicationMessage = createApplicationMessageHandler({
|
|
175
|
+
options,
|
|
176
|
+
signal: controller.signal,
|
|
177
|
+
reply: (message) => {
|
|
178
|
+
if (!destroyed)
|
|
179
|
+
iframe.contentWindow?.postMessage(
|
|
180
|
+
{ ...message, deploymentId: options.deploymentId },
|
|
181
|
+
chatOrigin
|
|
182
|
+
);
|
|
183
|
+
}
|
|
184
|
+
});
|
|
91
185
|
const receive = (event) => {
|
|
92
186
|
if (destroyed || event.source !== iframe.contentWindow || event.origin !== chatOrigin || event.data?.deploymentId !== options.deploymentId)
|
|
93
187
|
return;
|
|
188
|
+
if (event.data.type === "omni-chat:application-request" || event.data.type === "omni-chat:tool-request") {
|
|
189
|
+
void applicationMessage(event.data);
|
|
190
|
+
return;
|
|
191
|
+
}
|
|
94
192
|
if (event.data.type === "omni-chat:close-request") {
|
|
95
193
|
options.onRequestClose?.();
|
|
96
194
|
return;
|
|
@@ -106,7 +204,8 @@ function createOmniChatEmbed(container, options) {
|
|
|
106
204
|
{
|
|
107
205
|
type: "omni-chat:session",
|
|
108
206
|
deploymentId: options.deploymentId,
|
|
109
|
-
session
|
|
207
|
+
session,
|
|
208
|
+
...options.application ? { applicationEnabled: true } : {}
|
|
110
209
|
},
|
|
111
210
|
chatOrigin
|
|
112
211
|
);
|
|
@@ -118,7 +217,8 @@ function createOmniChatEmbed(container, options) {
|
|
|
118
217
|
{
|
|
119
218
|
type: "omni-chat:session-error",
|
|
120
219
|
deploymentId: options.deploymentId,
|
|
121
|
-
error: failure.message
|
|
220
|
+
error: failure.message,
|
|
221
|
+
...options.application ? { applicationEnabled: true } : {}
|
|
122
222
|
},
|
|
123
223
|
chatOrigin
|
|
124
224
|
);
|
|
@@ -163,11 +263,18 @@ function OmniChat({ className, style, title }) {
|
|
|
163
263
|
if (!options)
|
|
164
264
|
throw new Error("OmniChat must be rendered inside OmniProvider.");
|
|
165
265
|
const container = (0, import_react.useRef)(null);
|
|
166
|
-
const {
|
|
167
|
-
|
|
266
|
+
const {
|
|
267
|
+
baseUrl,
|
|
268
|
+
deploymentId,
|
|
269
|
+
getSession,
|
|
270
|
+
onError,
|
|
271
|
+
application,
|
|
272
|
+
frontendTools
|
|
273
|
+
} = options;
|
|
274
|
+
const callbacks = (0, import_react.useRef)({ getSession, onError, application, frontendTools });
|
|
168
275
|
(0, import_react.useEffect)(() => {
|
|
169
|
-
callbacks.current = { getSession, onError };
|
|
170
|
-
}, [getSession, onError]);
|
|
276
|
+
callbacks.current = { getSession, onError, application, frontendTools };
|
|
277
|
+
}, [getSession, onError, application, frontendTools]);
|
|
171
278
|
const hasSessionBroker = Boolean(getSession);
|
|
172
279
|
const frameTitle = title ?? options.title;
|
|
173
280
|
(0, import_react.useEffect)(() => {
|
|
@@ -177,7 +284,13 @@ function OmniChat({ className, style, title }) {
|
|
|
177
284
|
deploymentId,
|
|
178
285
|
getSession: hasSessionBroker ? () => callbacks.current.getSession() : void 0,
|
|
179
286
|
onError: (error) => callbacks.current.onError?.(error),
|
|
180
|
-
title: frameTitle
|
|
287
|
+
title: frameTitle,
|
|
288
|
+
get application() {
|
|
289
|
+
return callbacks.current.application;
|
|
290
|
+
},
|
|
291
|
+
get frontendTools() {
|
|
292
|
+
return callbacks.current.frontendTools;
|
|
293
|
+
}
|
|
181
294
|
});
|
|
182
295
|
return () => embed.destroy();
|
|
183
296
|
}, [baseUrl, deploymentId, frameTitle, hasSessionBroker]);
|
package/dist/react.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/react.tsx","../src/embed.ts"],"sourcesContent":["\"use client\";\n\nimport {\n createContext,\n useContext,\n useEffect,\n useRef,\n type CSSProperties,\n type ReactNode,\n} from \"react\";\nimport { createOmniChatEmbed, type OmniChatEmbedOptions } from \"./embed\";\n\nconst OmniChatContext = createContext<OmniChatEmbedOptions | null>(null);\nexport type OmniProviderProps = OmniChatEmbedOptions & { children: ReactNode };\n\nexport function OmniProvider({ children, ...options }: OmniProviderProps) {\n return (\n <OmniChatContext.Provider value={options}>\n {children}\n </OmniChatContext.Provider>\n );\n}\nexport interface OmniChatProps {\n className?: string;\n style?: CSSProperties;\n title?: string;\n}\n/** The hosted assistant owns its styles and runtime, so the host needs no Tailwind or assistant-ui dependencies. */\nexport function OmniChat({ className, style, title }: OmniChatProps) {\n const options = useContext(OmniChatContext);\n if (!options)\n throw new Error(\"OmniChat must be rendered inside OmniProvider.\");\n const container = useRef<HTMLDivElement>(null);\n const { baseUrl, deploymentId, getSession, onError } = options;\n const callbacks = useRef({ getSession, onError });\n useEffect(() => {\n callbacks.current = { getSession, onError };\n }, [getSession, onError]);\n const hasSessionBroker = Boolean(getSession);\n const frameTitle = title ?? options.title;\n useEffect(() => {\n if (!container.current) return;\n const embed = createOmniChatEmbed(container.current, {\n baseUrl,\n deploymentId,\n getSession: hasSessionBroker\n ? () => callbacks.current.getSession!()\n : undefined,\n onError: (error) => callbacks.current.onError?.(error),\n title: frameTitle,\n });\n return () => embed.destroy();\n }, [baseUrl, deploymentId, frameTitle, hasSessionBroker]);\n return (\n <div\n ref={container}\n className={className}\n style={{ height: \"100%\", minHeight: 320, ...style }}\n />\n );\n}\nexport type { OmniChatEmbedOptions, OmniChatSession } from \"./embed\";\n","/** A hosted chat session. Keep its short-lived token in memory, never in iframe URLs. */\nexport { createOmniChatWidget, type OmniChatWidgetOptions } from \"./widget\";\nexport { getOmniChatEmbedCode, getOmniChatReactCode } from \"./embed-code\";\nexport interface OmniChatSession {\n token: string;\n threadId: string;\n expiresIn: number;\n}\nexport interface OmniChatEmbedOptions {\n deploymentId: string;\n baseUrl?: string;\n /** Optional authenticated session mint on the host application's server. */\n getSession?: () => Promise<OmniChatSession>;\n title?: string;\n onError?: (error: Error) => void;\n /** Floating widgets use this to close on Escape from the hosted chat. */\n onRequestClose?: () => void;\n}\nexport interface OmniChatEmbed {\n iframe: HTMLIFrameElement;\n /** Focus the hosted composer, including when reopening a retained chat. */\n focus(): void;\n destroy(): void;\n}\n\nexport function getOmniChatEmbedUrl(\n options: Pick<OmniChatEmbedOptions, \"baseUrl\" | \"deploymentId\">,\n parentOrigin?: string,\n): string {\n if (\n !/^[\\da-f]{8}-[\\da-f]{4}-[\\da-f]{4}-[\\da-f]{4}-[\\da-f]{12}$/i.test(\n options.deploymentId,\n )\n )\n throw new Error(\"A valid chat deployment ID is required.\");\n const base = new URL(options.baseUrl ?? \"https://omnicommerce.sg\");\n if (\n (base.protocol !== \"https:\" &&\n !(\n base.protocol === \"http:\" &&\n [\"localhost\", \"127.0.0.1\"].includes(base.hostname)\n )) ||\n base.username ||\n base.password ||\n base.pathname !== \"/\" ||\n base.search ||\n base.hash\n )\n throw new Error(\"Chat baseUrl must be an HTTPS origin, or HTTP localhost.\");\n const url = new URL(`/embed/${options.deploymentId}`, base.origin);\n if (parentOrigin)\n url.searchParams.set(\"parentOrigin\", new URL(parentOrigin).origin);\n return url.toString();\n}\n\n/** Browser-only mounting; importing this entry point is safe during SSR. */\nexport function createOmniChatEmbed(\n container: HTMLElement,\n options: OmniChatEmbedOptions,\n): OmniChatEmbed {\n const document = container.ownerDocument;\n const host = document.defaultView;\n if (!host) throw new Error(\"A browser document is required to mount chat.\");\n const iframe = document.createElement(\"iframe\");\n iframe.src = getOmniChatEmbedUrl(options, host.location.origin);\n iframe.title = options.title ?? \"OmniCommerce assistant\";\n iframe.referrerPolicy = \"strict-origin-when-cross-origin\";\n iframe.setAttribute(\n \"sandbox\",\n \"allow-scripts allow-same-origin allow-forms allow-popups allow-popups-to-escape-sandbox\",\n );\n Object.assign(iframe.style, {\n width: \"100%\",\n height: \"100%\",\n minHeight: \"320px\",\n border: \"0\",\n display: \"block\",\n });\n const chatOrigin = new URL(iframe.src).origin;\n let destroyed = false;\n let pending: Promise<OmniChatSession> | undefined;\n let previousToken: string | undefined;\n const controller = new AbortController();\n\n const mint = async (): Promise<OmniChatSession> => {\n if (options.getSession) return options.getSession();\n const response = await host.fetch(\n `${chatOrigin}/api/embed/${options.deploymentId}/session`,\n {\n method: \"POST\",\n credentials: \"include\",\n signal: controller.signal,\n headers: {\n \"Content-Type\": \"application/json\",\n ...(previousToken\n ? { Authorization: `Bearer ${previousToken}` }\n : {}),\n },\n body: \"{}\",\n },\n );\n const result = (await response.json()) as OmniChatSession & {\n error?: string;\n };\n if (!response.ok) {\n // A rejected renewal must not keep Retry bound to an obsolete session.\n // The next mint still performs the deployment's full access checks.\n if (response.status === 401 || response.status === 403)\n previousToken = undefined;\n throw new Error(result.error ?? \"Could not open the chat session.\");\n }\n return result;\n };\n const receive = (event: MessageEvent) => {\n if (\n destroyed ||\n event.source !== iframe.contentWindow ||\n event.origin !== chatOrigin ||\n event.data?.deploymentId !== options.deploymentId\n )\n return;\n if (event.data.type === \"omni-chat:close-request\") {\n options.onRequestClose?.();\n return;\n }\n if (event.data.type !== \"omni-chat:session-request\") return;\n pending ??= mint();\n void pending\n .then((session) => {\n if (destroyed) return;\n if (\n typeof session?.token !== \"string\" ||\n !session.token ||\n session.token.length > 4096 ||\n typeof session.threadId !== \"string\" ||\n !session.threadId ||\n !Number.isFinite(session.expiresIn) ||\n session.expiresIn <= 0\n )\n throw new Error(\"Invalid chat session response.\");\n previousToken = session.token;\n iframe.contentWindow?.postMessage(\n {\n type: \"omni-chat:session\",\n deploymentId: options.deploymentId,\n session,\n },\n chatOrigin,\n );\n })\n .catch((error: unknown) => {\n if (destroyed) return;\n const failure =\n error instanceof Error ? error : new Error(\"Could not open chat.\");\n options.onError?.(failure);\n iframe.contentWindow?.postMessage(\n {\n type: \"omni-chat:session-error\",\n deploymentId: options.deploymentId,\n error: failure.message,\n },\n chatOrigin,\n );\n })\n .finally(() => {\n pending = undefined;\n });\n };\n host.addEventListener(\"message\", receive);\n container.append(iframe);\n return {\n iframe,\n focus() {\n if (destroyed) return;\n iframe.focus();\n iframe.contentWindow?.postMessage(\n {\n type: \"omni-chat:focus-composer\",\n deploymentId: options.deploymentId,\n },\n chatOrigin,\n );\n },\n destroy() {\n if (destroyed) return;\n destroyed = true;\n controller.abort();\n previousToken = undefined;\n host.removeEventListener(\"message\", receive);\n iframe.remove();\n },\n };\n}\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../src/react.tsx","../src/application.ts","../src/embed.ts"],"sourcesContent":["\"use client\";\n\nimport {\n createContext,\n useContext,\n useEffect,\n useRef,\n type CSSProperties,\n type ReactNode,\n} from \"react\";\nimport { createOmniChatEmbed, type OmniChatEmbedOptions } from \"./embed\";\n\nconst OmniChatContext = createContext<OmniChatEmbedOptions | null>(null);\nexport type OmniProviderProps = OmniChatEmbedOptions & { children: ReactNode };\n\nexport function OmniProvider({ children, ...options }: OmniProviderProps) {\n return (\n <OmniChatContext.Provider value={options}>\n {children}\n </OmniChatContext.Provider>\n );\n}\nexport interface OmniChatProps {\n className?: string;\n style?: CSSProperties;\n title?: string;\n}\n/** The hosted assistant owns its styles and runtime, so the host needs no Tailwind or assistant-ui dependencies. */\nexport function OmniChat({ className, style, title }: OmniChatProps) {\n const options = useContext(OmniChatContext);\n if (!options)\n throw new Error(\"OmniChat must be rendered inside OmniProvider.\");\n const container = useRef<HTMLDivElement>(null);\n const {\n baseUrl,\n deploymentId,\n getSession,\n onError,\n application,\n frontendTools,\n } = options;\n const callbacks = useRef({ getSession, onError, application, frontendTools });\n useEffect(() => {\n callbacks.current = { getSession, onError, application, frontendTools };\n }, [getSession, onError, application, frontendTools]);\n const hasSessionBroker = Boolean(getSession);\n const frameTitle = title ?? options.title;\n useEffect(() => {\n if (!container.current) return;\n const embed = createOmniChatEmbed(container.current, {\n baseUrl,\n deploymentId,\n getSession: hasSessionBroker\n ? () => callbacks.current.getSession!()\n : undefined,\n onError: (error) => callbacks.current.onError?.(error),\n title: frameTitle,\n get application() {\n return callbacks.current.application;\n },\n get frontendTools() {\n return callbacks.current.frontendTools;\n },\n });\n return () => embed.destroy();\n }, [baseUrl, deploymentId, frameTitle, hasSessionBroker]);\n return (\n <div\n ref={container}\n className={className}\n style={{ height: \"100%\", minHeight: 320, ...style }}\n />\n );\n}\nexport type {\n OmniFrontendTool,\n OmniChatApplication,\n OmniApplicationOptions,\n OmniChatEmbedOptions,\n OmniChatSession,\n} from \"./embed\";\n","/** Host-defined actions execute inside the application, never on the assistant server. */\nexport interface OmniFrontendTool {\n description: string;\n parameters: Record<string, unknown>;\n execute: (\n args: Record<string, unknown>,\n context: { toolCallId: string; abortSignal: AbortSignal },\n ) => unknown | Promise<unknown>;\n}\nexport interface OmniChatApplication {\n name: string;\n description: string;\n /** Return a small, explicitly selected snapshot. Do not include secrets. */\n getContext: () => unknown | Promise<unknown>;\n}\nexport interface OmniApplicationOptions {\n application?: OmniChatApplication;\n /** Names must start with app_; use array arguments for actions with multiple targets. */\n frontendTools?: Record<string, OmniFrontendTool>;\n}\n\nfunction boundedJson(value: unknown, maximum: number): unknown {\n const encoded = JSON.stringify(value);\n if (\n typeof encoded !== \"string\" ||\n new TextEncoder().encode(encoded).length > maximum ||\n /data:[^\"\\s]{0,100};base64,/i.test(encoded)\n )\n throw new Error(\n \"Application data must be bounded JSON without base64 payloads.\",\n );\n return JSON.parse(encoded);\n}\n\nexport function createApplicationMessageHandler(input: {\n options: OmniApplicationOptions;\n signal: AbortSignal;\n reply: (message: Record<string, unknown>) => void;\n}) {\n // A repeated parked call reuses its result instead of applying an action twice.\n const calls = new Map<\n string,\n { signature: string; result: Promise<unknown> }\n >();\n return async (message: Record<string, unknown>) => {\n if (typeof message.requestId !== \"string\" || message.requestId.length > 200)\n return;\n try {\n if (message.type === \"omni-chat:application-request\") {\n const application = input.options.application;\n input.reply({\n type: \"omni-chat:application-response\",\n requestId: message.requestId,\n application: application\n ? boundedJson(\n {\n name: application.name,\n description: application.description,\n context: (await application.getContext()) ?? null,\n tools: Object.entries(input.options.frontendTools ?? {}).map(\n ([name, definition]) => ({\n name,\n description: definition.description,\n parameters: definition.parameters,\n }),\n ),\n },\n 65536,\n )\n : undefined,\n });\n } else if (message.type === \"omni-chat:tool-request\") {\n const name = message.toolName;\n const toolCallId = message.toolCallId;\n if (\n typeof name !== \"string\" ||\n !/^app_[a-z][a-z0-9_]{0,59}$/.test(name) ||\n typeof toolCallId !== \"string\" ||\n !toolCallId ||\n toolCallId.length > 200\n )\n throw new Error(\"Invalid application tool request.\");\n const definition = Object.hasOwn(\n input.options.frontendTools ?? {},\n name,\n )\n ? input.options.frontendTools?.[name]\n : undefined;\n if (!input.options.application || !definition)\n throw new Error(\"This application action is unavailable.\");\n const args = message.args;\n if (!args || typeof args !== \"object\" || Array.isArray(args))\n throw new Error(\"Invalid application action arguments.\");\n const signature = JSON.stringify({ name, args });\n const prior = calls.get(toolCallId);\n if (prior && prior.signature !== signature)\n throw new Error(\"This tool call ID was already used.\");\n const result =\n prior?.result ??\n Promise.resolve().then(() =>\n definition.execute(args as Record<string, unknown>, {\n toolCallId,\n abortSignal: input.signal,\n }),\n );\n if (!prior) {\n if (calls.size >= 200)\n throw new Error(\"Start a new application chat.\");\n calls.set(toolCallId, { signature, result });\n }\n input.reply({\n type: \"omni-chat:tool-response\",\n requestId: message.requestId,\n toolCallId,\n result: boundedJson((await result) ?? null, 12000),\n });\n }\n } catch (error) {\n input.reply({\n type:\n message.type === \"omni-chat:application-request\"\n ? \"omni-chat:application-response\"\n : \"omni-chat:tool-response\",\n requestId: message.requestId,\n toolCallId: message.toolCallId,\n error:\n error instanceof Error\n ? error.message\n : \"The application action failed.\",\n });\n }\n };\n}\n","import {\n createApplicationMessageHandler,\n type OmniApplicationOptions,\n} from \"./application\";\nexport type {\n OmniFrontendTool,\n OmniChatApplication,\n OmniApplicationOptions,\n} from \"./application\";\n/** A hosted chat session. Keep its short-lived token in memory, never in iframe URLs. */\nexport { createOmniChatWidget, type OmniChatWidgetOptions } from \"./widget\";\nexport { getOmniChatEmbedCode, getOmniChatReactCode } from \"./embed-code\";\nexport interface OmniChatSession {\n token: string;\n threadId: string;\n expiresIn: number;\n}\nexport interface OmniChatEmbedOptions extends OmniApplicationOptions {\n deploymentId: string;\n baseUrl?: string;\n /** Optional authenticated session mint on the host application's server. */\n getSession?: () => Promise<OmniChatSession>;\n title?: string;\n onError?: (error: Error) => void;\n /** Floating widgets use this to close on Escape from the hosted chat. */\n onRequestClose?: () => void;\n}\nexport interface OmniChatEmbed {\n iframe: HTMLIFrameElement;\n /** Focus the hosted composer, including when reopening a retained chat. */\n focus(): void;\n destroy(): void;\n}\n\nexport function getOmniChatEmbedUrl(\n options: Pick<OmniChatEmbedOptions, \"baseUrl\" | \"deploymentId\">,\n parentOrigin?: string,\n): string {\n if (\n !/^[\\da-f]{8}-[\\da-f]{4}-[\\da-f]{4}-[\\da-f]{4}-[\\da-f]{12}$/i.test(\n options.deploymentId,\n )\n )\n throw new Error(\"A valid chat deployment ID is required.\");\n const base = new URL(options.baseUrl ?? \"https://omnicommerce.sg\");\n if (\n (base.protocol !== \"https:\" &&\n !(\n base.protocol === \"http:\" &&\n [\"localhost\", \"127.0.0.1\"].includes(base.hostname)\n )) ||\n base.username ||\n base.password ||\n base.pathname !== \"/\" ||\n base.search ||\n base.hash\n )\n throw new Error(\"Chat baseUrl must be an HTTPS origin, or HTTP localhost.\");\n const url = new URL(`/embed/${options.deploymentId}`, base.origin);\n if (parentOrigin)\n url.searchParams.set(\"parentOrigin\", new URL(parentOrigin).origin);\n return url.toString();\n}\n\n/** Browser-only mounting; importing this entry point is safe during SSR. */\nexport function createOmniChatEmbed(\n container: HTMLElement,\n options: OmniChatEmbedOptions,\n): OmniChatEmbed {\n const document = container.ownerDocument;\n const host = document.defaultView;\n if (!host) throw new Error(\"A browser document is required to mount chat.\");\n const iframe = document.createElement(\"iframe\");\n iframe.src = getOmniChatEmbedUrl(options, host.location.origin);\n iframe.title = options.title ?? \"OmniCommerce assistant\";\n iframe.referrerPolicy = \"strict-origin-when-cross-origin\";\n iframe.setAttribute(\n \"sandbox\",\n \"allow-scripts allow-same-origin allow-forms allow-popups allow-popups-to-escape-sandbox\",\n );\n Object.assign(iframe.style, {\n width: \"100%\",\n height: \"100%\",\n minHeight: \"320px\",\n border: \"0\",\n display: \"block\",\n });\n const chatOrigin = new URL(iframe.src).origin;\n let destroyed = false;\n let pending: Promise<OmniChatSession> | undefined;\n let previousToken: string | undefined;\n const controller = new AbortController();\n\n const mint = async (): Promise<OmniChatSession> => {\n if (options.getSession) return options.getSession();\n const response = await host.fetch(\n `${chatOrigin}/api/embed/${options.deploymentId}/session`,\n {\n method: \"POST\",\n credentials: \"include\",\n signal: controller.signal,\n headers: {\n \"Content-Type\": \"application/json\",\n ...(previousToken\n ? { Authorization: `Bearer ${previousToken}` }\n : {}),\n },\n body: \"{}\",\n },\n );\n const result = (await response.json()) as OmniChatSession & {\n error?: string;\n };\n if (!response.ok) {\n // A rejected renewal must not keep Retry bound to an obsolete session.\n // The next mint still performs the deployment's full access checks.\n if (response.status === 401 || response.status === 403)\n previousToken = undefined;\n throw new Error(result.error ?? \"Could not open the chat session.\");\n }\n return result;\n };\n const applicationMessage = createApplicationMessageHandler({\n options,\n signal: controller.signal,\n reply: (message) => {\n if (!destroyed)\n iframe.contentWindow?.postMessage(\n { ...message, deploymentId: options.deploymentId },\n chatOrigin,\n );\n },\n });\n const receive = (event: MessageEvent) => {\n if (\n destroyed ||\n event.source !== iframe.contentWindow ||\n event.origin !== chatOrigin ||\n event.data?.deploymentId !== options.deploymentId\n )\n return;\n if (\n event.data.type === \"omni-chat:application-request\" ||\n event.data.type === \"omni-chat:tool-request\"\n ) {\n void applicationMessage(event.data);\n return;\n }\n if (event.data.type === \"omni-chat:close-request\") {\n options.onRequestClose?.();\n return;\n }\n if (event.data.type !== \"omni-chat:session-request\") return;\n pending ??= mint();\n void pending\n .then((session) => {\n if (destroyed) return;\n if (\n typeof session?.token !== \"string\" ||\n !session.token ||\n session.token.length > 4096 ||\n typeof session.threadId !== \"string\" ||\n !session.threadId ||\n !Number.isFinite(session.expiresIn) ||\n session.expiresIn <= 0\n )\n throw new Error(\"Invalid chat session response.\");\n previousToken = session.token;\n iframe.contentWindow?.postMessage(\n {\n type: \"omni-chat:session\",\n deploymentId: options.deploymentId,\n session,\n ...(options.application ? { applicationEnabled: true } : {}),\n },\n chatOrigin,\n );\n })\n .catch((error: unknown) => {\n if (destroyed) return;\n const failure =\n error instanceof Error ? error : new Error(\"Could not open chat.\");\n options.onError?.(failure);\n iframe.contentWindow?.postMessage(\n {\n type: \"omni-chat:session-error\",\n deploymentId: options.deploymentId,\n error: failure.message,\n ...(options.application ? {applicationEnabled: true} : {}),\n },\n chatOrigin,\n );\n })\n .finally(() => {\n pending = undefined;\n });\n };\n host.addEventListener(\"message\", receive);\n container.append(iframe);\n return {\n iframe,\n focus() {\n if (destroyed) return;\n iframe.focus();\n iframe.contentWindow?.postMessage(\n {\n type: \"omni-chat:focus-composer\",\n deploymentId: options.deploymentId,\n },\n chatOrigin,\n );\n },\n destroy() {\n if (destroyed) return;\n destroyed = true;\n controller.abort();\n previousToken = undefined;\n host.removeEventListener(\"message\", receive);\n iframe.remove();\n },\n };\n}\n"],"mappings":"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","names":[]}
|
package/dist/react.d.cts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import * as react_jsx_runtime from 'react/jsx-runtime';
|
|
2
2
|
import { CSSProperties, ReactNode } from 'react';
|
|
3
3
|
import { OmniChatEmbedOptions } from './embed.cjs';
|
|
4
|
-
export { OmniChatSession } from './embed.cjs';
|
|
4
|
+
export { OmniApplicationOptions, OmniChatApplication, OmniChatSession, OmniFrontendTool } from './embed.cjs';
|
|
5
5
|
|
|
6
6
|
type OmniProviderProps = OmniChatEmbedOptions & {
|
|
7
7
|
children: ReactNode;
|
package/dist/react.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import * as react_jsx_runtime from 'react/jsx-runtime';
|
|
2
2
|
import { CSSProperties, ReactNode } from 'react';
|
|
3
3
|
import { OmniChatEmbedOptions } from './embed.js';
|
|
4
|
-
export { OmniChatSession } from './embed.js';
|
|
4
|
+
export { OmniApplicationOptions, OmniChatApplication, OmniChatSession, OmniFrontendTool } from './embed.js';
|
|
5
5
|
|
|
6
6
|
type OmniProviderProps = OmniChatEmbedOptions & {
|
|
7
7
|
children: ReactNode;
|
package/dist/react.js
CHANGED
|
@@ -8,6 +8,89 @@ import {
|
|
|
8
8
|
useRef
|
|
9
9
|
} from "react";
|
|
10
10
|
|
|
11
|
+
// src/application.ts
|
|
12
|
+
function boundedJson(value, maximum) {
|
|
13
|
+
const encoded = JSON.stringify(value);
|
|
14
|
+
if (typeof encoded !== "string" || new TextEncoder().encode(encoded).length > maximum || /data:[^"\s]{0,100};base64,/i.test(encoded))
|
|
15
|
+
throw new Error(
|
|
16
|
+
"Application data must be bounded JSON without base64 payloads."
|
|
17
|
+
);
|
|
18
|
+
return JSON.parse(encoded);
|
|
19
|
+
}
|
|
20
|
+
function createApplicationMessageHandler(input) {
|
|
21
|
+
const calls = /* @__PURE__ */ new Map();
|
|
22
|
+
return async (message) => {
|
|
23
|
+
if (typeof message.requestId !== "string" || message.requestId.length > 200)
|
|
24
|
+
return;
|
|
25
|
+
try {
|
|
26
|
+
if (message.type === "omni-chat:application-request") {
|
|
27
|
+
const application = input.options.application;
|
|
28
|
+
input.reply({
|
|
29
|
+
type: "omni-chat:application-response",
|
|
30
|
+
requestId: message.requestId,
|
|
31
|
+
application: application ? boundedJson(
|
|
32
|
+
{
|
|
33
|
+
name: application.name,
|
|
34
|
+
description: application.description,
|
|
35
|
+
context: await application.getContext() ?? null,
|
|
36
|
+
tools: Object.entries(input.options.frontendTools ?? {}).map(
|
|
37
|
+
([name, definition]) => ({
|
|
38
|
+
name,
|
|
39
|
+
description: definition.description,
|
|
40
|
+
parameters: definition.parameters
|
|
41
|
+
})
|
|
42
|
+
)
|
|
43
|
+
},
|
|
44
|
+
65536
|
|
45
|
+
) : void 0
|
|
46
|
+
});
|
|
47
|
+
} else if (message.type === "omni-chat:tool-request") {
|
|
48
|
+
const name = message.toolName;
|
|
49
|
+
const toolCallId = message.toolCallId;
|
|
50
|
+
if (typeof name !== "string" || !/^app_[a-z][a-z0-9_]{0,59}$/.test(name) || typeof toolCallId !== "string" || !toolCallId || toolCallId.length > 200)
|
|
51
|
+
throw new Error("Invalid application tool request.");
|
|
52
|
+
const definition = Object.hasOwn(
|
|
53
|
+
input.options.frontendTools ?? {},
|
|
54
|
+
name
|
|
55
|
+
) ? input.options.frontendTools?.[name] : void 0;
|
|
56
|
+
if (!input.options.application || !definition)
|
|
57
|
+
throw new Error("This application action is unavailable.");
|
|
58
|
+
const args = message.args;
|
|
59
|
+
if (!args || typeof args !== "object" || Array.isArray(args))
|
|
60
|
+
throw new Error("Invalid application action arguments.");
|
|
61
|
+
const signature = JSON.stringify({ name, args });
|
|
62
|
+
const prior = calls.get(toolCallId);
|
|
63
|
+
if (prior && prior.signature !== signature)
|
|
64
|
+
throw new Error("This tool call ID was already used.");
|
|
65
|
+
const result = prior?.result ?? Promise.resolve().then(
|
|
66
|
+
() => definition.execute(args, {
|
|
67
|
+
toolCallId,
|
|
68
|
+
abortSignal: input.signal
|
|
69
|
+
})
|
|
70
|
+
);
|
|
71
|
+
if (!prior) {
|
|
72
|
+
if (calls.size >= 200)
|
|
73
|
+
throw new Error("Start a new application chat.");
|
|
74
|
+
calls.set(toolCallId, { signature, result });
|
|
75
|
+
}
|
|
76
|
+
input.reply({
|
|
77
|
+
type: "omni-chat:tool-response",
|
|
78
|
+
requestId: message.requestId,
|
|
79
|
+
toolCallId,
|
|
80
|
+
result: boundedJson(await result ?? null, 12e3)
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
} catch (error) {
|
|
84
|
+
input.reply({
|
|
85
|
+
type: message.type === "omni-chat:application-request" ? "omni-chat:application-response" : "omni-chat:tool-response",
|
|
86
|
+
requestId: message.requestId,
|
|
87
|
+
toolCallId: message.toolCallId,
|
|
88
|
+
error: error instanceof Error ? error.message : "The application action failed."
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
|
|
11
94
|
// src/embed.ts
|
|
12
95
|
function getOmniChatEmbedUrl(options, parentOrigin) {
|
|
13
96
|
if (!/^[\da-f]{8}-[\da-f]{4}-[\da-f]{4}-[\da-f]{4}-[\da-f]{12}$/i.test(
|
|
@@ -69,9 +152,24 @@ function createOmniChatEmbed(container, options) {
|
|
|
69
152
|
}
|
|
70
153
|
return result;
|
|
71
154
|
};
|
|
155
|
+
const applicationMessage = createApplicationMessageHandler({
|
|
156
|
+
options,
|
|
157
|
+
signal: controller.signal,
|
|
158
|
+
reply: (message) => {
|
|
159
|
+
if (!destroyed)
|
|
160
|
+
iframe.contentWindow?.postMessage(
|
|
161
|
+
{ ...message, deploymentId: options.deploymentId },
|
|
162
|
+
chatOrigin
|
|
163
|
+
);
|
|
164
|
+
}
|
|
165
|
+
});
|
|
72
166
|
const receive = (event) => {
|
|
73
167
|
if (destroyed || event.source !== iframe.contentWindow || event.origin !== chatOrigin || event.data?.deploymentId !== options.deploymentId)
|
|
74
168
|
return;
|
|
169
|
+
if (event.data.type === "omni-chat:application-request" || event.data.type === "omni-chat:tool-request") {
|
|
170
|
+
void applicationMessage(event.data);
|
|
171
|
+
return;
|
|
172
|
+
}
|
|
75
173
|
if (event.data.type === "omni-chat:close-request") {
|
|
76
174
|
options.onRequestClose?.();
|
|
77
175
|
return;
|
|
@@ -87,7 +185,8 @@ function createOmniChatEmbed(container, options) {
|
|
|
87
185
|
{
|
|
88
186
|
type: "omni-chat:session",
|
|
89
187
|
deploymentId: options.deploymentId,
|
|
90
|
-
session
|
|
188
|
+
session,
|
|
189
|
+
...options.application ? { applicationEnabled: true } : {}
|
|
91
190
|
},
|
|
92
191
|
chatOrigin
|
|
93
192
|
);
|
|
@@ -99,7 +198,8 @@ function createOmniChatEmbed(container, options) {
|
|
|
99
198
|
{
|
|
100
199
|
type: "omni-chat:session-error",
|
|
101
200
|
deploymentId: options.deploymentId,
|
|
102
|
-
error: failure.message
|
|
201
|
+
error: failure.message,
|
|
202
|
+
...options.application ? { applicationEnabled: true } : {}
|
|
103
203
|
},
|
|
104
204
|
chatOrigin
|
|
105
205
|
);
|
|
@@ -144,11 +244,18 @@ function OmniChat({ className, style, title }) {
|
|
|
144
244
|
if (!options)
|
|
145
245
|
throw new Error("OmniChat must be rendered inside OmniProvider.");
|
|
146
246
|
const container = useRef(null);
|
|
147
|
-
const {
|
|
148
|
-
|
|
247
|
+
const {
|
|
248
|
+
baseUrl,
|
|
249
|
+
deploymentId,
|
|
250
|
+
getSession,
|
|
251
|
+
onError,
|
|
252
|
+
application,
|
|
253
|
+
frontendTools
|
|
254
|
+
} = options;
|
|
255
|
+
const callbacks = useRef({ getSession, onError, application, frontendTools });
|
|
149
256
|
useEffect(() => {
|
|
150
|
-
callbacks.current = { getSession, onError };
|
|
151
|
-
}, [getSession, onError]);
|
|
257
|
+
callbacks.current = { getSession, onError, application, frontendTools };
|
|
258
|
+
}, [getSession, onError, application, frontendTools]);
|
|
152
259
|
const hasSessionBroker = Boolean(getSession);
|
|
153
260
|
const frameTitle = title ?? options.title;
|
|
154
261
|
useEffect(() => {
|
|
@@ -158,7 +265,13 @@ function OmniChat({ className, style, title }) {
|
|
|
158
265
|
deploymentId,
|
|
159
266
|
getSession: hasSessionBroker ? () => callbacks.current.getSession() : void 0,
|
|
160
267
|
onError: (error) => callbacks.current.onError?.(error),
|
|
161
|
-
title: frameTitle
|
|
268
|
+
title: frameTitle,
|
|
269
|
+
get application() {
|
|
270
|
+
return callbacks.current.application;
|
|
271
|
+
},
|
|
272
|
+
get frontendTools() {
|
|
273
|
+
return callbacks.current.frontendTools;
|
|
274
|
+
}
|
|
162
275
|
});
|
|
163
276
|
return () => embed.destroy();
|
|
164
277
|
}, [baseUrl, deploymentId, frameTitle, hasSessionBroker]);
|
package/dist/react.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/react.tsx","../src/embed.ts"],"sourcesContent":["\"use client\";\n\nimport {\n createContext,\n useContext,\n useEffect,\n useRef,\n type CSSProperties,\n type ReactNode,\n} from \"react\";\nimport { createOmniChatEmbed, type OmniChatEmbedOptions } from \"./embed\";\n\nconst OmniChatContext = createContext<OmniChatEmbedOptions | null>(null);\nexport type OmniProviderProps = OmniChatEmbedOptions & { children: ReactNode };\n\nexport function OmniProvider({ children, ...options }: OmniProviderProps) {\n return (\n <OmniChatContext.Provider value={options}>\n {children}\n </OmniChatContext.Provider>\n );\n}\nexport interface OmniChatProps {\n className?: string;\n style?: CSSProperties;\n title?: string;\n}\n/** The hosted assistant owns its styles and runtime, so the host needs no Tailwind or assistant-ui dependencies. */\nexport function OmniChat({ className, style, title }: OmniChatProps) {\n const options = useContext(OmniChatContext);\n if (!options)\n throw new Error(\"OmniChat must be rendered inside OmniProvider.\");\n const container = useRef<HTMLDivElement>(null);\n const { baseUrl, deploymentId, getSession, onError } = options;\n const callbacks = useRef({ getSession, onError });\n useEffect(() => {\n callbacks.current = { getSession, onError };\n }, [getSession, onError]);\n const hasSessionBroker = Boolean(getSession);\n const frameTitle = title ?? options.title;\n useEffect(() => {\n if (!container.current) return;\n const embed = createOmniChatEmbed(container.current, {\n baseUrl,\n deploymentId,\n getSession: hasSessionBroker\n ? () => callbacks.current.getSession!()\n : undefined,\n onError: (error) => callbacks.current.onError?.(error),\n title: frameTitle,\n });\n return () => embed.destroy();\n }, [baseUrl, deploymentId, frameTitle, hasSessionBroker]);\n return (\n <div\n ref={container}\n className={className}\n style={{ height: \"100%\", minHeight: 320, ...style }}\n />\n );\n}\nexport type { OmniChatEmbedOptions, OmniChatSession } from \"./embed\";\n","/** A hosted chat session. Keep its short-lived token in memory, never in iframe URLs. */\nexport { createOmniChatWidget, type OmniChatWidgetOptions } from \"./widget\";\nexport { getOmniChatEmbedCode, getOmniChatReactCode } from \"./embed-code\";\nexport interface OmniChatSession {\n token: string;\n threadId: string;\n expiresIn: number;\n}\nexport interface OmniChatEmbedOptions {\n deploymentId: string;\n baseUrl?: string;\n /** Optional authenticated session mint on the host application's server. */\n getSession?: () => Promise<OmniChatSession>;\n title?: string;\n onError?: (error: Error) => void;\n /** Floating widgets use this to close on Escape from the hosted chat. */\n onRequestClose?: () => void;\n}\nexport interface OmniChatEmbed {\n iframe: HTMLIFrameElement;\n /** Focus the hosted composer, including when reopening a retained chat. */\n focus(): void;\n destroy(): void;\n}\n\nexport function getOmniChatEmbedUrl(\n options: Pick<OmniChatEmbedOptions, \"baseUrl\" | \"deploymentId\">,\n parentOrigin?: string,\n): string {\n if (\n !/^[\\da-f]{8}-[\\da-f]{4}-[\\da-f]{4}-[\\da-f]{4}-[\\da-f]{12}$/i.test(\n options.deploymentId,\n )\n )\n throw new Error(\"A valid chat deployment ID is required.\");\n const base = new URL(options.baseUrl ?? \"https://omnicommerce.sg\");\n if (\n (base.protocol !== \"https:\" &&\n !(\n base.protocol === \"http:\" &&\n [\"localhost\", \"127.0.0.1\"].includes(base.hostname)\n )) ||\n base.username ||\n base.password ||\n base.pathname !== \"/\" ||\n base.search ||\n base.hash\n )\n throw new Error(\"Chat baseUrl must be an HTTPS origin, or HTTP localhost.\");\n const url = new URL(`/embed/${options.deploymentId}`, base.origin);\n if (parentOrigin)\n url.searchParams.set(\"parentOrigin\", new URL(parentOrigin).origin);\n return url.toString();\n}\n\n/** Browser-only mounting; importing this entry point is safe during SSR. */\nexport function createOmniChatEmbed(\n container: HTMLElement,\n options: OmniChatEmbedOptions,\n): OmniChatEmbed {\n const document = container.ownerDocument;\n const host = document.defaultView;\n if (!host) throw new Error(\"A browser document is required to mount chat.\");\n const iframe = document.createElement(\"iframe\");\n iframe.src = getOmniChatEmbedUrl(options, host.location.origin);\n iframe.title = options.title ?? \"OmniCommerce assistant\";\n iframe.referrerPolicy = \"strict-origin-when-cross-origin\";\n iframe.setAttribute(\n \"sandbox\",\n \"allow-scripts allow-same-origin allow-forms allow-popups allow-popups-to-escape-sandbox\",\n );\n Object.assign(iframe.style, {\n width: \"100%\",\n height: \"100%\",\n minHeight: \"320px\",\n border: \"0\",\n display: \"block\",\n });\n const chatOrigin = new URL(iframe.src).origin;\n let destroyed = false;\n let pending: Promise<OmniChatSession> | undefined;\n let previousToken: string | undefined;\n const controller = new AbortController();\n\n const mint = async (): Promise<OmniChatSession> => {\n if (options.getSession) return options.getSession();\n const response = await host.fetch(\n `${chatOrigin}/api/embed/${options.deploymentId}/session`,\n {\n method: \"POST\",\n credentials: \"include\",\n signal: controller.signal,\n headers: {\n \"Content-Type\": \"application/json\",\n ...(previousToken\n ? { Authorization: `Bearer ${previousToken}` }\n : {}),\n },\n body: \"{}\",\n },\n );\n const result = (await response.json()) as OmniChatSession & {\n error?: string;\n };\n if (!response.ok) {\n // A rejected renewal must not keep Retry bound to an obsolete session.\n // The next mint still performs the deployment's full access checks.\n if (response.status === 401 || response.status === 403)\n previousToken = undefined;\n throw new Error(result.error ?? \"Could not open the chat session.\");\n }\n return result;\n };\n const receive = (event: MessageEvent) => {\n if (\n destroyed ||\n event.source !== iframe.contentWindow ||\n event.origin !== chatOrigin ||\n event.data?.deploymentId !== options.deploymentId\n )\n return;\n if (event.data.type === \"omni-chat:close-request\") {\n options.onRequestClose?.();\n return;\n }\n if (event.data.type !== \"omni-chat:session-request\") return;\n pending ??= mint();\n void pending\n .then((session) => {\n if (destroyed) return;\n if (\n typeof session?.token !== \"string\" ||\n !session.token ||\n session.token.length > 4096 ||\n typeof session.threadId !== \"string\" ||\n !session.threadId ||\n !Number.isFinite(session.expiresIn) ||\n session.expiresIn <= 0\n )\n throw new Error(\"Invalid chat session response.\");\n previousToken = session.token;\n iframe.contentWindow?.postMessage(\n {\n type: \"omni-chat:session\",\n deploymentId: options.deploymentId,\n session,\n },\n chatOrigin,\n );\n })\n .catch((error: unknown) => {\n if (destroyed) return;\n const failure =\n error instanceof Error ? error : new Error(\"Could not open chat.\");\n options.onError?.(failure);\n iframe.contentWindow?.postMessage(\n {\n type: \"omni-chat:session-error\",\n deploymentId: options.deploymentId,\n error: failure.message,\n },\n chatOrigin,\n );\n })\n .finally(() => {\n pending = undefined;\n });\n };\n host.addEventListener(\"message\", receive);\n container.append(iframe);\n return {\n iframe,\n focus() {\n if (destroyed) return;\n iframe.focus();\n iframe.contentWindow?.postMessage(\n {\n type: \"omni-chat:focus-composer\",\n deploymentId: options.deploymentId,\n },\n chatOrigin,\n );\n },\n destroy() {\n if (destroyed) return;\n destroyed = true;\n controller.abort();\n previousToken = undefined;\n host.removeEventListener(\"message\", receive);\n iframe.remove();\n },\n };\n}\n"],"mappings":";;;AAEA;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OAGK;;;ACgBA,SAAS,oBACd,SACA,cACQ;AACR,MACE,CAAC,6DAA6D;AAAA,IAC5D,QAAQ;AAAA,EACV;AAEA,UAAM,IAAI,MAAM,yCAAyC;AAC3D,QAAM,OAAO,IAAI,IAAI,QAAQ,WAAW,yBAAyB;AACjE,MACG,KAAK,aAAa,YACjB,EACE,KAAK,aAAa,WAClB,CAAC,aAAa,WAAW,EAAE,SAAS,KAAK,QAAQ,MAErD,KAAK,YACL,KAAK,YACL,KAAK,aAAa,OAClB,KAAK,UACL,KAAK;AAEL,UAAM,IAAI,MAAM,0DAA0D;AAC5E,QAAM,MAAM,IAAI,IAAI,UAAU,QAAQ,YAAY,IAAI,KAAK,MAAM;AACjE,MAAI;AACF,QAAI,aAAa,IAAI,gBAAgB,IAAI,IAAI,YAAY,EAAE,MAAM;AACnE,SAAO,IAAI,SAAS;AACtB;AAGO,SAAS,oBACd,WACA,SACe;AACf,QAAM,WAAW,UAAU;AAC3B,QAAM,OAAO,SAAS;AACtB,MAAI,CAAC,KAAM,OAAM,IAAI,MAAM,+CAA+C;AAC1E,QAAM,SAAS,SAAS,cAAc,QAAQ;AAC9C,SAAO,MAAM,oBAAoB,SAAS,KAAK,SAAS,MAAM;AAC9D,SAAO,QAAQ,QAAQ,SAAS;AAChC,SAAO,iBAAiB;AACxB,SAAO;AAAA,IACL;AAAA,IACA;AAAA,EACF;AACA,SAAO,OAAO,OAAO,OAAO;AAAA,IAC1B,OAAO;AAAA,IACP,QAAQ;AAAA,IACR,WAAW;AAAA,IACX,QAAQ;AAAA,IACR,SAAS;AAAA,EACX,CAAC;AACD,QAAM,aAAa,IAAI,IAAI,OAAO,GAAG,EAAE;AACvC,MAAI,YAAY;AAChB,MAAI;AACJ,MAAI;AACJ,QAAM,aAAa,IAAI,gBAAgB;AAEvC,QAAM,OAAO,YAAsC;AACjD,QAAI,QAAQ,WAAY,QAAO,QAAQ,WAAW;AAClD,UAAM,WAAW,MAAM,KAAK;AAAA,MAC1B,GAAG,UAAU,cAAc,QAAQ,YAAY;AAAA,MAC/C;AAAA,QACE,QAAQ;AAAA,QACR,aAAa;AAAA,QACb,QAAQ,WAAW;AAAA,QACnB,SAAS;AAAA,UACP,gBAAgB;AAAA,UAChB,GAAI,gBACA,EAAE,eAAe,UAAU,aAAa,GAAG,IAC3C,CAAC;AAAA,QACP;AAAA,QACA,MAAM;AAAA,MACR;AAAA,IACF;AACA,UAAM,SAAU,MAAM,SAAS,KAAK;AAGpC,QAAI,CAAC,SAAS,IAAI;AAGhB,UAAI,SAAS,WAAW,OAAO,SAAS,WAAW;AACjD,wBAAgB;AAClB,YAAM,IAAI,MAAM,OAAO,SAAS,kCAAkC;AAAA,IACpE;AACA,WAAO;AAAA,EACT;AACA,QAAM,UAAU,CAAC,UAAwB;AACvC,QACE,aACA,MAAM,WAAW,OAAO,iBACxB,MAAM,WAAW,cACjB,MAAM,MAAM,iBAAiB,QAAQ;AAErC;AACF,QAAI,MAAM,KAAK,SAAS,2BAA2B;AACjD,cAAQ,iBAAiB;AACzB;AAAA,IACF;AACA,QAAI,MAAM,KAAK,SAAS,4BAA6B;AACrD,0BAAY,KAAK;AACjB,SAAK,QACF,KAAK,CAAC,YAAY;AACjB,UAAI,UAAW;AACf,UACE,OAAO,SAAS,UAAU,YAC1B,CAAC,QAAQ,SACT,QAAQ,MAAM,SAAS,QACvB,OAAO,QAAQ,aAAa,YAC5B,CAAC,QAAQ,YACT,CAAC,OAAO,SAAS,QAAQ,SAAS,KAClC,QAAQ,aAAa;AAErB,cAAM,IAAI,MAAM,gCAAgC;AAClD,sBAAgB,QAAQ;AACxB,aAAO,eAAe;AAAA,QACpB;AAAA,UACE,MAAM;AAAA,UACN,cAAc,QAAQ;AAAA,UACtB;AAAA,QACF;AAAA,QACA;AAAA,MACF;AAAA,IACF,CAAC,EACA,MAAM,CAAC,UAAmB;AACzB,UAAI,UAAW;AACf,YAAM,UACJ,iBAAiB,QAAQ,QAAQ,IAAI,MAAM,sBAAsB;AACnE,cAAQ,UAAU,OAAO;AACzB,aAAO,eAAe;AAAA,QACpB;AAAA,UACE,MAAM;AAAA,UACN,cAAc,QAAQ;AAAA,UACtB,OAAO,QAAQ;AAAA,QACjB;AAAA,QACA;AAAA,MACF;AAAA,IACF,CAAC,EACA,QAAQ,MAAM;AACb,gBAAU;AAAA,IACZ,CAAC;AAAA,EACL;AACA,OAAK,iBAAiB,WAAW,OAAO;AACxC,YAAU,OAAO,MAAM;AACvB,SAAO;AAAA,IACL;AAAA,IACA,QAAQ;AACN,UAAI,UAAW;AACf,aAAO,MAAM;AACb,aAAO,eAAe;AAAA,QACpB;AAAA,UACE,MAAM;AAAA,UACN,cAAc,QAAQ;AAAA,QACxB;AAAA,QACA;AAAA,MACF;AAAA,IACF;AAAA,IACA,UAAU;AACR,UAAI,UAAW;AACf,kBAAY;AACZ,iBAAW,MAAM;AACjB,sBAAgB;AAChB,WAAK,oBAAoB,WAAW,OAAO;AAC3C,aAAO,OAAO;AAAA,IAChB;AAAA,EACF;AACF;;;AD/KI;AALJ,IAAM,kBAAkB,cAA2C,IAAI;AAGhE,SAAS,aAAa,EAAE,UAAU,GAAG,QAAQ,GAAsB;AACxE,SACE,oBAAC,gBAAgB,UAAhB,EAAyB,OAAO,SAC9B,UACH;AAEJ;AAOO,SAAS,SAAS,EAAE,WAAW,OAAO,MAAM,GAAkB;AACnE,QAAM,UAAU,WAAW,eAAe;AAC1C,MAAI,CAAC;AACH,UAAM,IAAI,MAAM,gDAAgD;AAClE,QAAM,YAAY,OAAuB,IAAI;AAC7C,QAAM,EAAE,SAAS,cAAc,YAAY,QAAQ,IAAI;AACvD,QAAM,YAAY,OAAO,EAAE,YAAY,QAAQ,CAAC;AAChD,YAAU,MAAM;AACd,cAAU,UAAU,EAAE,YAAY,QAAQ;AAAA,EAC5C,GAAG,CAAC,YAAY,OAAO,CAAC;AACxB,QAAM,mBAAmB,QAAQ,UAAU;AAC3C,QAAM,aAAa,SAAS,QAAQ;AACpC,YAAU,MAAM;AACd,QAAI,CAAC,UAAU,QAAS;AACxB,UAAM,QAAQ,oBAAoB,UAAU,SAAS;AAAA,MACnD;AAAA,MACA;AAAA,MACA,YAAY,mBACR,MAAM,UAAU,QAAQ,WAAY,IACpC;AAAA,MACJ,SAAS,CAAC,UAAU,UAAU,QAAQ,UAAU,KAAK;AAAA,MACrD,OAAO;AAAA,IACT,CAAC;AACD,WAAO,MAAM,MAAM,QAAQ;AAAA,EAC7B,GAAG,CAAC,SAAS,cAAc,YAAY,gBAAgB,CAAC;AACxD,SACE;AAAA,IAAC;AAAA;AAAA,MACC,KAAK;AAAA,MACL;AAAA,MACA,OAAO,EAAE,QAAQ,QAAQ,WAAW,KAAK,GAAG,MAAM;AAAA;AAAA,EACpD;AAEJ;","names":[]}
|
|
1
|
+
{"version":3,"sources":["../src/react.tsx","../src/application.ts","../src/embed.ts"],"sourcesContent":["\"use client\";\n\nimport {\n createContext,\n useContext,\n useEffect,\n useRef,\n type CSSProperties,\n type ReactNode,\n} from \"react\";\nimport { createOmniChatEmbed, type OmniChatEmbedOptions } from \"./embed\";\n\nconst OmniChatContext = createContext<OmniChatEmbedOptions | null>(null);\nexport type OmniProviderProps = OmniChatEmbedOptions & { children: ReactNode };\n\nexport function OmniProvider({ children, ...options }: OmniProviderProps) {\n return (\n <OmniChatContext.Provider value={options}>\n {children}\n </OmniChatContext.Provider>\n );\n}\nexport interface OmniChatProps {\n className?: string;\n style?: CSSProperties;\n title?: string;\n}\n/** The hosted assistant owns its styles and runtime, so the host needs no Tailwind or assistant-ui dependencies. */\nexport function OmniChat({ className, style, title }: OmniChatProps) {\n const options = useContext(OmniChatContext);\n if (!options)\n throw new Error(\"OmniChat must be rendered inside OmniProvider.\");\n const container = useRef<HTMLDivElement>(null);\n const {\n baseUrl,\n deploymentId,\n getSession,\n onError,\n application,\n frontendTools,\n } = options;\n const callbacks = useRef({ getSession, onError, application, frontendTools });\n useEffect(() => {\n callbacks.current = { getSession, onError, application, frontendTools };\n }, [getSession, onError, application, frontendTools]);\n const hasSessionBroker = Boolean(getSession);\n const frameTitle = title ?? options.title;\n useEffect(() => {\n if (!container.current) return;\n const embed = createOmniChatEmbed(container.current, {\n baseUrl,\n deploymentId,\n getSession: hasSessionBroker\n ? () => callbacks.current.getSession!()\n : undefined,\n onError: (error) => callbacks.current.onError?.(error),\n title: frameTitle,\n get application() {\n return callbacks.current.application;\n },\n get frontendTools() {\n return callbacks.current.frontendTools;\n },\n });\n return () => embed.destroy();\n }, [baseUrl, deploymentId, frameTitle, hasSessionBroker]);\n return (\n <div\n ref={container}\n className={className}\n style={{ height: \"100%\", minHeight: 320, ...style }}\n />\n );\n}\nexport type {\n OmniFrontendTool,\n OmniChatApplication,\n OmniApplicationOptions,\n OmniChatEmbedOptions,\n OmniChatSession,\n} from \"./embed\";\n","/** Host-defined actions execute inside the application, never on the assistant server. */\nexport interface OmniFrontendTool {\n description: string;\n parameters: Record<string, unknown>;\n execute: (\n args: Record<string, unknown>,\n context: { toolCallId: string; abortSignal: AbortSignal },\n ) => unknown | Promise<unknown>;\n}\nexport interface OmniChatApplication {\n name: string;\n description: string;\n /** Return a small, explicitly selected snapshot. Do not include secrets. */\n getContext: () => unknown | Promise<unknown>;\n}\nexport interface OmniApplicationOptions {\n application?: OmniChatApplication;\n /** Names must start with app_; use array arguments for actions with multiple targets. */\n frontendTools?: Record<string, OmniFrontendTool>;\n}\n\nfunction boundedJson(value: unknown, maximum: number): unknown {\n const encoded = JSON.stringify(value);\n if (\n typeof encoded !== \"string\" ||\n new TextEncoder().encode(encoded).length > maximum ||\n /data:[^\"\\s]{0,100};base64,/i.test(encoded)\n )\n throw new Error(\n \"Application data must be bounded JSON without base64 payloads.\",\n );\n return JSON.parse(encoded);\n}\n\nexport function createApplicationMessageHandler(input: {\n options: OmniApplicationOptions;\n signal: AbortSignal;\n reply: (message: Record<string, unknown>) => void;\n}) {\n // A repeated parked call reuses its result instead of applying an action twice.\n const calls = new Map<\n string,\n { signature: string; result: Promise<unknown> }\n >();\n return async (message: Record<string, unknown>) => {\n if (typeof message.requestId !== \"string\" || message.requestId.length > 200)\n return;\n try {\n if (message.type === \"omni-chat:application-request\") {\n const application = input.options.application;\n input.reply({\n type: \"omni-chat:application-response\",\n requestId: message.requestId,\n application: application\n ? boundedJson(\n {\n name: application.name,\n description: application.description,\n context: (await application.getContext()) ?? null,\n tools: Object.entries(input.options.frontendTools ?? {}).map(\n ([name, definition]) => ({\n name,\n description: definition.description,\n parameters: definition.parameters,\n }),\n ),\n },\n 65536,\n )\n : undefined,\n });\n } else if (message.type === \"omni-chat:tool-request\") {\n const name = message.toolName;\n const toolCallId = message.toolCallId;\n if (\n typeof name !== \"string\" ||\n !/^app_[a-z][a-z0-9_]{0,59}$/.test(name) ||\n typeof toolCallId !== \"string\" ||\n !toolCallId ||\n toolCallId.length > 200\n )\n throw new Error(\"Invalid application tool request.\");\n const definition = Object.hasOwn(\n input.options.frontendTools ?? {},\n name,\n )\n ? input.options.frontendTools?.[name]\n : undefined;\n if (!input.options.application || !definition)\n throw new Error(\"This application action is unavailable.\");\n const args = message.args;\n if (!args || typeof args !== \"object\" || Array.isArray(args))\n throw new Error(\"Invalid application action arguments.\");\n const signature = JSON.stringify({ name, args });\n const prior = calls.get(toolCallId);\n if (prior && prior.signature !== signature)\n throw new Error(\"This tool call ID was already used.\");\n const result =\n prior?.result ??\n Promise.resolve().then(() =>\n definition.execute(args as Record<string, unknown>, {\n toolCallId,\n abortSignal: input.signal,\n }),\n );\n if (!prior) {\n if (calls.size >= 200)\n throw new Error(\"Start a new application chat.\");\n calls.set(toolCallId, { signature, result });\n }\n input.reply({\n type: \"omni-chat:tool-response\",\n requestId: message.requestId,\n toolCallId,\n result: boundedJson((await result) ?? null, 12000),\n });\n }\n } catch (error) {\n input.reply({\n type:\n message.type === \"omni-chat:application-request\"\n ? \"omni-chat:application-response\"\n : \"omni-chat:tool-response\",\n requestId: message.requestId,\n toolCallId: message.toolCallId,\n error:\n error instanceof Error\n ? error.message\n : \"The application action failed.\",\n });\n }\n };\n}\n","import {\n createApplicationMessageHandler,\n type OmniApplicationOptions,\n} from \"./application\";\nexport type {\n OmniFrontendTool,\n OmniChatApplication,\n OmniApplicationOptions,\n} from \"./application\";\n/** A hosted chat session. Keep its short-lived token in memory, never in iframe URLs. */\nexport { createOmniChatWidget, type OmniChatWidgetOptions } from \"./widget\";\nexport { getOmniChatEmbedCode, getOmniChatReactCode } from \"./embed-code\";\nexport interface OmniChatSession {\n token: string;\n threadId: string;\n expiresIn: number;\n}\nexport interface OmniChatEmbedOptions extends OmniApplicationOptions {\n deploymentId: string;\n baseUrl?: string;\n /** Optional authenticated session mint on the host application's server. */\n getSession?: () => Promise<OmniChatSession>;\n title?: string;\n onError?: (error: Error) => void;\n /** Floating widgets use this to close on Escape from the hosted chat. */\n onRequestClose?: () => void;\n}\nexport interface OmniChatEmbed {\n iframe: HTMLIFrameElement;\n /** Focus the hosted composer, including when reopening a retained chat. */\n focus(): void;\n destroy(): void;\n}\n\nexport function getOmniChatEmbedUrl(\n options: Pick<OmniChatEmbedOptions, \"baseUrl\" | \"deploymentId\">,\n parentOrigin?: string,\n): string {\n if (\n !/^[\\da-f]{8}-[\\da-f]{4}-[\\da-f]{4}-[\\da-f]{4}-[\\da-f]{12}$/i.test(\n options.deploymentId,\n )\n )\n throw new Error(\"A valid chat deployment ID is required.\");\n const base = new URL(options.baseUrl ?? \"https://omnicommerce.sg\");\n if (\n (base.protocol !== \"https:\" &&\n !(\n base.protocol === \"http:\" &&\n [\"localhost\", \"127.0.0.1\"].includes(base.hostname)\n )) ||\n base.username ||\n base.password ||\n base.pathname !== \"/\" ||\n base.search ||\n base.hash\n )\n throw new Error(\"Chat baseUrl must be an HTTPS origin, or HTTP localhost.\");\n const url = new URL(`/embed/${options.deploymentId}`, base.origin);\n if (parentOrigin)\n url.searchParams.set(\"parentOrigin\", new URL(parentOrigin).origin);\n return url.toString();\n}\n\n/** Browser-only mounting; importing this entry point is safe during SSR. */\nexport function createOmniChatEmbed(\n container: HTMLElement,\n options: OmniChatEmbedOptions,\n): OmniChatEmbed {\n const document = container.ownerDocument;\n const host = document.defaultView;\n if (!host) throw new Error(\"A browser document is required to mount chat.\");\n const iframe = document.createElement(\"iframe\");\n iframe.src = getOmniChatEmbedUrl(options, host.location.origin);\n iframe.title = options.title ?? \"OmniCommerce assistant\";\n iframe.referrerPolicy = \"strict-origin-when-cross-origin\";\n iframe.setAttribute(\n \"sandbox\",\n \"allow-scripts allow-same-origin allow-forms allow-popups allow-popups-to-escape-sandbox\",\n );\n Object.assign(iframe.style, {\n width: \"100%\",\n height: \"100%\",\n minHeight: \"320px\",\n border: \"0\",\n display: \"block\",\n });\n const chatOrigin = new URL(iframe.src).origin;\n let destroyed = false;\n let pending: Promise<OmniChatSession> | undefined;\n let previousToken: string | undefined;\n const controller = new AbortController();\n\n const mint = async (): Promise<OmniChatSession> => {\n if (options.getSession) return options.getSession();\n const response = await host.fetch(\n `${chatOrigin}/api/embed/${options.deploymentId}/session`,\n {\n method: \"POST\",\n credentials: \"include\",\n signal: controller.signal,\n headers: {\n \"Content-Type\": \"application/json\",\n ...(previousToken\n ? { Authorization: `Bearer ${previousToken}` }\n : {}),\n },\n body: \"{}\",\n },\n );\n const result = (await response.json()) as OmniChatSession & {\n error?: string;\n };\n if (!response.ok) {\n // A rejected renewal must not keep Retry bound to an obsolete session.\n // The next mint still performs the deployment's full access checks.\n if (response.status === 401 || response.status === 403)\n previousToken = undefined;\n throw new Error(result.error ?? \"Could not open the chat session.\");\n }\n return result;\n };\n const applicationMessage = createApplicationMessageHandler({\n options,\n signal: controller.signal,\n reply: (message) => {\n if (!destroyed)\n iframe.contentWindow?.postMessage(\n { ...message, deploymentId: options.deploymentId },\n chatOrigin,\n );\n },\n });\n const receive = (event: MessageEvent) => {\n if (\n destroyed ||\n event.source !== iframe.contentWindow ||\n event.origin !== chatOrigin ||\n event.data?.deploymentId !== options.deploymentId\n )\n return;\n if (\n event.data.type === \"omni-chat:application-request\" ||\n event.data.type === \"omni-chat:tool-request\"\n ) {\n void applicationMessage(event.data);\n return;\n }\n if (event.data.type === \"omni-chat:close-request\") {\n options.onRequestClose?.();\n return;\n }\n if (event.data.type !== \"omni-chat:session-request\") return;\n pending ??= mint();\n void pending\n .then((session) => {\n if (destroyed) return;\n if (\n typeof session?.token !== \"string\" ||\n !session.token ||\n session.token.length > 4096 ||\n typeof session.threadId !== \"string\" ||\n !session.threadId ||\n !Number.isFinite(session.expiresIn) ||\n session.expiresIn <= 0\n )\n throw new Error(\"Invalid chat session response.\");\n previousToken = session.token;\n iframe.contentWindow?.postMessage(\n {\n type: \"omni-chat:session\",\n deploymentId: options.deploymentId,\n session,\n ...(options.application ? { applicationEnabled: true } : {}),\n },\n chatOrigin,\n );\n })\n .catch((error: unknown) => {\n if (destroyed) return;\n const failure =\n error instanceof Error ? error : new Error(\"Could not open chat.\");\n options.onError?.(failure);\n iframe.contentWindow?.postMessage(\n {\n type: \"omni-chat:session-error\",\n deploymentId: options.deploymentId,\n error: failure.message,\n ...(options.application ? {applicationEnabled: true} : {}),\n },\n chatOrigin,\n );\n })\n .finally(() => {\n pending = undefined;\n });\n };\n host.addEventListener(\"message\", receive);\n container.append(iframe);\n return {\n iframe,\n focus() {\n if (destroyed) return;\n iframe.focus();\n iframe.contentWindow?.postMessage(\n {\n type: \"omni-chat:focus-composer\",\n deploymentId: options.deploymentId,\n },\n chatOrigin,\n );\n },\n destroy() {\n if (destroyed) return;\n destroyed = true;\n controller.abort();\n previousToken = undefined;\n host.removeEventListener(\"message\", receive);\n iframe.remove();\n },\n };\n}\n"],"mappings":"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","names":[]}
|