@noodleseed/assistant 1.13.0 → 1.15.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 +5 -0
- package/dist/{chunk-KRB2SXFD.js → chunk-RRJIK473.js} +211 -51
- package/dist/chunk-RRJIK473.js.map +1 -0
- package/dist/{chunk-ZPELSHUP.js → chunk-SUHNNWKP.js} +37 -20
- package/dist/chunk-SUHNNWKP.js.map +1 -0
- package/dist/{client-BKIeuSVS.d.cts → client-B_l59rWg.d.cts} +29 -2
- package/dist/{client-BccAX_om.d.ts → client-MnLfiyo2.d.ts} +29 -2
- package/dist/client.cjs +206 -50
- package/dist/client.cjs.map +1 -1
- package/dist/client.d.cts +1 -1
- package/dist/client.d.ts +1 -1
- package/dist/client.js +1 -1
- package/dist/embed.global.js +540 -0
- package/dist/index.cjs +242 -67
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +6 -2
- package/dist/index.d.ts +6 -2
- package/dist/index.js +2 -2
- package/dist/react/client.cjs +217 -54
- package/dist/react/client.cjs.map +1 -1
- package/dist/react/client.d.cts +3 -3
- package/dist/react/client.d.ts +3 -3
- package/dist/react/client.js +12 -5
- package/dist/react/client.js.map +1 -1
- package/dist/react.cjs +251 -70
- package/dist/react.cjs.map +1 -1
- package/dist/react.d.cts +7 -2
- package/dist/react.d.ts +7 -2
- package/dist/react.js +11 -5
- package/dist/react.js.map +1 -1
- package/package.json +1 -1
- package/dist/chunk-KRB2SXFD.js.map +0 -1
- package/dist/chunk-ZPELSHUP.js.map +0 -1
package/dist/react/client.d.cts
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
|
-
import { A as AssistantPageContext, a as AssistantContext, C as CreateAssistantClientOptions, b as AssistantChatState, c as AssistantClient } from '../client-
|
|
1
|
+
import { A as AssistantPageContext, a as AssistantContext, C as CreateAssistantClientOptions, b as AssistantChatState, c as AssistantClient } from '../client-B_l59rWg.cjs';
|
|
2
2
|
import '../appearance-Cc-eqSbp.cjs';
|
|
3
3
|
import 'ai';
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
type UseNoodleAssistantOptions<TPageContext extends AssistantPageContext = AssistantContext> = CreateAssistantClientOptions<TPageContext> & {
|
|
6
6
|
/**
|
|
7
7
|
* Browser-local identity for the authenticated principal/tenant.
|
|
8
8
|
* Changing it discards the previous in-memory session and transcript; it is never sent to Noodle.
|
|
9
9
|
*/
|
|
10
10
|
readonly principalKey: string;
|
|
11
|
-
}
|
|
11
|
+
};
|
|
12
12
|
type UseNoodleAssistantResult<TPageContext extends AssistantPageContext = AssistantContext> = AssistantChatState & {
|
|
13
13
|
/** Canonical controller for turns, interactions, context, Apps requests, and aborts. */
|
|
14
14
|
readonly client: AssistantClient<TPageContext>;
|
package/dist/react/client.d.ts
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
|
-
import { A as AssistantPageContext, a as AssistantContext, C as CreateAssistantClientOptions, b as AssistantChatState, c as AssistantClient } from '../client-
|
|
1
|
+
import { A as AssistantPageContext, a as AssistantContext, C as CreateAssistantClientOptions, b as AssistantChatState, c as AssistantClient } from '../client-MnLfiyo2.js';
|
|
2
2
|
import '../appearance-Cc-eqSbp.js';
|
|
3
3
|
import 'ai';
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
type UseNoodleAssistantOptions<TPageContext extends AssistantPageContext = AssistantContext> = CreateAssistantClientOptions<TPageContext> & {
|
|
6
6
|
/**
|
|
7
7
|
* Browser-local identity for the authenticated principal/tenant.
|
|
8
8
|
* Changing it discards the previous in-memory session and transcript; it is never sent to Noodle.
|
|
9
9
|
*/
|
|
10
10
|
readonly principalKey: string;
|
|
11
|
-
}
|
|
11
|
+
};
|
|
12
12
|
type UseNoodleAssistantResult<TPageContext extends AssistantPageContext = AssistantContext> = AssistantChatState & {
|
|
13
13
|
/** Canonical controller for turns, interactions, context, Apps requests, and aborts. */
|
|
14
14
|
readonly client: AssistantClient<TPageContext>;
|
package/dist/react/client.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import {
|
|
3
3
|
createAssistantClient
|
|
4
|
-
} from "../chunk-
|
|
4
|
+
} from "../chunk-RRJIK473.js";
|
|
5
5
|
import "../chunk-3YYTUJPJ.js";
|
|
6
6
|
import "../chunk-5FUTL2UF.js";
|
|
7
7
|
|
|
@@ -10,16 +10,23 @@ import { useEffect, useLayoutEffect, useMemo, useRef, useSyncExternalStore } fro
|
|
|
10
10
|
var useCommittedLayoutEffect = typeof window === "undefined" ? useEffect : useLayoutEffect;
|
|
11
11
|
function useNoodleAssistant(options) {
|
|
12
12
|
const optionsRef = useRef(options);
|
|
13
|
-
const { fetch: injectedFetch, principalKey, sessionEndpoint } = options;
|
|
13
|
+
const { fetch: injectedFetch, principalKey, sessionEndpoint, embedId, serviceUrl } = options;
|
|
14
14
|
const client = useMemo(() => {
|
|
15
15
|
if (!principalKey.trim()) throw new Error("principalKey is required");
|
|
16
|
-
|
|
17
|
-
sessionEndpoint,
|
|
16
|
+
const behavior = {
|
|
18
17
|
...injectedFetch ? { fetch: injectedFetch } : {},
|
|
19
18
|
clientContext: () => readProvider(optionsRef.current.clientContext),
|
|
20
19
|
pageContext: () => readProvider(optionsRef.current.pageContext)
|
|
20
|
+
};
|
|
21
|
+
return embedId !== void 0 ? createAssistantClient({
|
|
22
|
+
embedId,
|
|
23
|
+
...serviceUrl !== void 0 ? { serviceUrl } : {},
|
|
24
|
+
...behavior
|
|
25
|
+
}) : createAssistantClient({
|
|
26
|
+
sessionEndpoint: sessionEndpoint ?? "",
|
|
27
|
+
...behavior
|
|
21
28
|
});
|
|
22
|
-
}, [injectedFetch, principalKey, sessionEndpoint]);
|
|
29
|
+
}, [injectedFetch, principalKey, sessionEndpoint, embedId, serviceUrl]);
|
|
23
30
|
const store = useMemo(() => createAssistantReactStore(client), [client]);
|
|
24
31
|
const state = useSyncExternalStore(store.subscribe, store.getSnapshot, store.getSnapshot);
|
|
25
32
|
const synchronized = useRef(void 0);
|
package/dist/react/client.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/react-client.ts"],"sourcesContent":["'use client';\n\nimport { useEffect, useLayoutEffect, useMemo, useRef, useSyncExternalStore } from 'react';\nimport {\n type AssistantChatState,\n type AssistantClient,\n type AssistantContext,\n type AssistantModelContextUpdate,\n type AssistantPageContext,\n type CreateAssistantClientOptions,\n createAssistantClient,\n} from './client.js';\n\nexport
|
|
1
|
+
{"version":3,"sources":["../../src/react-client.ts"],"sourcesContent":["'use client';\n\nimport { useEffect, useLayoutEffect, useMemo, useRef, useSyncExternalStore } from 'react';\nimport {\n type AssistantChatState,\n type AssistantClient,\n type AssistantContext,\n type AssistantModelContextUpdate,\n type AssistantPageContext,\n type CreateAssistantClientOptions,\n createAssistantClient,\n} from './client.js';\n\nexport type UseNoodleAssistantOptions<\n TPageContext extends AssistantPageContext = AssistantContext,\n> = CreateAssistantClientOptions<TPageContext> & {\n /**\n * Browser-local identity for the authenticated principal/tenant.\n * Changing it discards the previous in-memory session and transcript; it is never sent to Noodle.\n */\n readonly principalKey: string;\n};\n\nexport type UseNoodleAssistantResult<TPageContext extends AssistantPageContext = AssistantContext> =\n AssistantChatState & {\n /** Canonical controller for turns, interactions, context, Apps requests, and aborts. */\n readonly client: AssistantClient<TPageContext>;\n };\n\ninterface AssistantReactStore {\n readonly getSnapshot: () => AssistantChatState;\n readonly subscribe: (listener: () => void) => () => void;\n}\n\nconst useCommittedLayoutEffect = typeof window === 'undefined' ? useEffect : useLayoutEffect;\n\n/**\n * Subscribe a customer-owned React renderer to the DOM-free assistant client.\n *\n * The hook owns only client lifetime and React state projection. Session exchange, transport,\n * retries, interaction continuations, and transcript construction remain client responsibilities.\n */\nexport function useNoodleAssistant<TPageContext extends AssistantPageContext = AssistantContext>(\n options: UseNoodleAssistantOptions<TPageContext>,\n): UseNoodleAssistantResult<TPageContext> {\n const optionsRef = useRef(options);\n const { fetch: injectedFetch, principalKey, sessionEndpoint, embedId, serviceUrl } = options;\n\n const client = useMemo(() => {\n if (!principalKey.trim()) throw new Error('principalKey is required');\n // The two mounts are spelled out rather than spread from one object: the exclusive union is what\n // stops a caller passing both, and a spread would collapse it back into an ambiguous shape.\n const behavior = {\n ...(injectedFetch ? { fetch: injectedFetch } : {}),\n clientContext: () => readProvider(optionsRef.current.clientContext),\n pageContext: () => readProvider(optionsRef.current.pageContext),\n };\n return embedId !== undefined\n ? createAssistantClient<TPageContext>({\n embedId,\n ...(serviceUrl !== undefined ? { serviceUrl } : {}),\n ...behavior,\n })\n : createAssistantClient<TPageContext>({\n sessionEndpoint: sessionEndpoint ?? '',\n ...behavior,\n });\n }, [injectedFetch, principalKey, sessionEndpoint, embedId, serviceUrl]);\n const store = useMemo(() => createAssistantReactStore(client), [client]);\n const state = useSyncExternalStore(store.subscribe, store.getSnapshot, store.getSnapshot);\n const synchronized = useRef<\n | {\n readonly client: AssistantClient<TPageContext>;\n readonly context: AssistantContext | undefined;\n readonly modelContext: AssistantModelContextUpdate | undefined;\n }\n | undefined\n >(undefined);\n\n useCommittedLayoutEffect(() => {\n optionsRef.current = options;\n const previous = synchronized.current;\n if (previous?.client === client) {\n if (previous.context !== options.context) {\n client.updateContext(options.context ?? {});\n }\n if (previous.modelContext !== options.modelContext) {\n client.updateModelContext(options.modelContext ?? {});\n }\n } else {\n if (options.context !== undefined) client.updateContext(options.context);\n if (options.modelContext !== undefined) client.updateModelContext(options.modelContext);\n }\n synchronized.current = {\n client,\n context: options.context,\n modelContext: options.modelContext,\n };\n }, [client, options]);\n\n useCommittedLayoutEffect(\n () => () => {\n client.abort();\n client.resetSession();\n },\n [client],\n );\n\n return useMemo(() => ({ client, ...state }), [client, state]);\n}\n\nfunction createAssistantReactStore<TPageContext extends AssistantPageContext>(\n client: AssistantClient<TPageContext>,\n): AssistantReactStore {\n let snapshot = client.getChatState();\n return {\n getSnapshot: () => snapshot,\n subscribe(listener) {\n let subscribing = true;\n const unsubscribe = client.subscribeChat((next) => {\n snapshot = next;\n if (!subscribing) listener();\n });\n subscribing = false;\n return unsubscribe;\n },\n };\n}\n\nfunction readProvider<T>(value: T | (() => T | undefined) | undefined): T | undefined {\n return typeof value === 'function' ? (value as () => T | undefined)() : value;\n}\n"],"mappings":";;;;;;;;AAEA,SAAS,WAAW,iBAAiB,SAAS,QAAQ,4BAA4B;AAgClF,IAAM,2BAA2B,OAAO,WAAW,cAAc,YAAY;AAQtE,SAAS,mBACd,SACwC;AACxC,QAAM,aAAa,OAAO,OAAO;AACjC,QAAM,EAAE,OAAO,eAAe,cAAc,iBAAiB,SAAS,WAAW,IAAI;AAErF,QAAM,SAAS,QAAQ,MAAM;AAC3B,QAAI,CAAC,aAAa,KAAK,EAAG,OAAM,IAAI,MAAM,0BAA0B;AAGpE,UAAM,WAAW;AAAA,MACf,GAAI,gBAAgB,EAAE,OAAO,cAAc,IAAI,CAAC;AAAA,MAChD,eAAe,MAAM,aAAa,WAAW,QAAQ,aAAa;AAAA,MAClE,aAAa,MAAM,aAAa,WAAW,QAAQ,WAAW;AAAA,IAChE;AACA,WAAO,YAAY,SACf,sBAAoC;AAAA,MAClC;AAAA,MACA,GAAI,eAAe,SAAY,EAAE,WAAW,IAAI,CAAC;AAAA,MACjD,GAAG;AAAA,IACL,CAAC,IACD,sBAAoC;AAAA,MAClC,iBAAiB,mBAAmB;AAAA,MACpC,GAAG;AAAA,IACL,CAAC;AAAA,EACP,GAAG,CAAC,eAAe,cAAc,iBAAiB,SAAS,UAAU,CAAC;AACtE,QAAM,QAAQ,QAAQ,MAAM,0BAA0B,MAAM,GAAG,CAAC,MAAM,CAAC;AACvE,QAAM,QAAQ,qBAAqB,MAAM,WAAW,MAAM,aAAa,MAAM,WAAW;AACxF,QAAM,eAAe,OAOnB,MAAS;AAEX,2BAAyB,MAAM;AAC7B,eAAW,UAAU;AACrB,UAAM,WAAW,aAAa;AAC9B,QAAI,UAAU,WAAW,QAAQ;AAC/B,UAAI,SAAS,YAAY,QAAQ,SAAS;AACxC,eAAO,cAAc,QAAQ,WAAW,CAAC,CAAC;AAAA,MAC5C;AACA,UAAI,SAAS,iBAAiB,QAAQ,cAAc;AAClD,eAAO,mBAAmB,QAAQ,gBAAgB,CAAC,CAAC;AAAA,MACtD;AAAA,IACF,OAAO;AACL,UAAI,QAAQ,YAAY,OAAW,QAAO,cAAc,QAAQ,OAAO;AACvE,UAAI,QAAQ,iBAAiB,OAAW,QAAO,mBAAmB,QAAQ,YAAY;AAAA,IACxF;AACA,iBAAa,UAAU;AAAA,MACrB;AAAA,MACA,SAAS,QAAQ;AAAA,MACjB,cAAc,QAAQ;AAAA,IACxB;AAAA,EACF,GAAG,CAAC,QAAQ,OAAO,CAAC;AAEpB;AAAA,IACE,MAAM,MAAM;AACV,aAAO,MAAM;AACb,aAAO,aAAa;AAAA,IACtB;AAAA,IACA,CAAC,MAAM;AAAA,EACT;AAEA,SAAO,QAAQ,OAAO,EAAE,QAAQ,GAAG,MAAM,IAAI,CAAC,QAAQ,KAAK,CAAC;AAC9D;AAEA,SAAS,0BACP,QACqB;AACrB,MAAI,WAAW,OAAO,aAAa;AACnC,SAAO;AAAA,IACL,aAAa,MAAM;AAAA,IACnB,UAAU,UAAU;AAClB,UAAI,cAAc;AAClB,YAAM,cAAc,OAAO,cAAc,CAAC,SAAS;AACjD,mBAAW;AACX,YAAI,CAAC,YAAa,UAAS;AAAA,MAC7B,CAAC;AACD,oBAAc;AACd,aAAO;AAAA,IACT;AAAA,EACF;AACF;AAEA,SAAS,aAAgB,OAA6D;AACpF,SAAO,OAAO,UAAU,aAAc,MAA8B,IAAI;AAC1E;","names":[]}
|
package/dist/react.cjs
CHANGED
|
@@ -22191,7 +22191,7 @@ function mountAssistantApp(detail, actions, options = {}) {
|
|
|
22191
22191
|
const current = options.getDetail?.() ?? detail;
|
|
22192
22192
|
void bridge.sendSandboxResourceReady({
|
|
22193
22193
|
html: current.html,
|
|
22194
|
-
sandbox: "allow-scripts
|
|
22194
|
+
sandbox: "allow-scripts",
|
|
22195
22195
|
...current.resourceMeta ?? {}
|
|
22196
22196
|
});
|
|
22197
22197
|
};
|
|
@@ -26820,7 +26820,7 @@ var AssistantChatStateStore = class {
|
|
|
26820
26820
|
this.#writeText(event.data.delta);
|
|
26821
26821
|
return;
|
|
26822
26822
|
case "tool_proposed":
|
|
26823
|
-
this.#writeConfirmation(event.data);
|
|
26823
|
+
this.#writeStandaloneData(() => this.#writeConfirmation(event.data));
|
|
26824
26824
|
return;
|
|
26825
26825
|
case "interaction_proposed":
|
|
26826
26826
|
this.#writeData("confirmation", event.data.id, {
|
|
@@ -26829,10 +26829,12 @@ var AssistantChatStateStore = class {
|
|
|
26829
26829
|
});
|
|
26830
26830
|
return;
|
|
26831
26831
|
case "input_requested":
|
|
26832
|
-
this.#
|
|
26833
|
-
|
|
26834
|
-
|
|
26835
|
-
|
|
26832
|
+
this.#writeStandaloneData(
|
|
26833
|
+
() => this.#writeData("input-request", event.data.id, {
|
|
26834
|
+
...event.data,
|
|
26835
|
+
status: "pending"
|
|
26836
|
+
})
|
|
26837
|
+
);
|
|
26836
26838
|
return;
|
|
26837
26839
|
case "tool_completed":
|
|
26838
26840
|
this.#writeData("tool-result", event.data.id, event.data);
|
|
@@ -26939,6 +26941,12 @@ var AssistantChatStateStore = class {
|
|
|
26939
26941
|
#writeConfirmation(data) {
|
|
26940
26942
|
this.#writeData("confirmation", data.id, { ...data, status: "pending" });
|
|
26941
26943
|
}
|
|
26944
|
+
#writeStandaloneData(write) {
|
|
26945
|
+
const standalone = this.#operation === void 0;
|
|
26946
|
+
if (standalone) this.#startOperation();
|
|
26947
|
+
write();
|
|
26948
|
+
if (standalone) this.#closeOperation("ready");
|
|
26949
|
+
}
|
|
26942
26950
|
#writeData(name21, id, data) {
|
|
26943
26951
|
const operation = this.#operation;
|
|
26944
26952
|
if (!operation || operation.closed) return;
|
|
@@ -27271,6 +27279,24 @@ function hasToJsonProperty(value) {
|
|
|
27271
27279
|
return false;
|
|
27272
27280
|
}
|
|
27273
27281
|
|
|
27282
|
+
// src/session-source.ts
|
|
27283
|
+
var NOODLE_CLOUD_URL = "https://cloud.noodleseed.dev";
|
|
27284
|
+
var PUBLIC_SESSION_PATH = "/v1/assistant/public-sessions";
|
|
27285
|
+
function resolveSessionSource(options) {
|
|
27286
|
+
const { sessionEndpoint, embedId, serviceUrl } = options;
|
|
27287
|
+
if (Boolean(embedId) === Boolean(sessionEndpoint)) {
|
|
27288
|
+
throw new Error("pass either embedId or sessionEndpoint, not both and not neither");
|
|
27289
|
+
}
|
|
27290
|
+
if (embedId) {
|
|
27291
|
+
const base = (serviceUrl ?? NOODLE_CLOUD_URL).replace(/\/+$/, "");
|
|
27292
|
+
return { kind: "public", url: `${base}${PUBLIC_SESSION_PATH}`, embedId };
|
|
27293
|
+
}
|
|
27294
|
+
return { kind: "exchange", url: sessionEndpoint };
|
|
27295
|
+
}
|
|
27296
|
+
function sessionSourceKey(options) {
|
|
27297
|
+
return `${options.embedId ?? ""}|${options.serviceUrl ?? ""}|${options.sessionEndpoint ?? ""}`;
|
|
27298
|
+
}
|
|
27299
|
+
|
|
27274
27300
|
// src/transport.ts
|
|
27275
27301
|
var AssistantTransportError = class extends Error {
|
|
27276
27302
|
code = "invalid_response";
|
|
@@ -27398,6 +27424,19 @@ function isAbortError2(error51) {
|
|
|
27398
27424
|
}
|
|
27399
27425
|
|
|
27400
27426
|
// src/client.ts
|
|
27427
|
+
async function refusalCode(response) {
|
|
27428
|
+
try {
|
|
27429
|
+
const body = await response.clone().json();
|
|
27430
|
+
const code = body.code;
|
|
27431
|
+
return typeof code === "string" ? code : void 0;
|
|
27432
|
+
} catch {
|
|
27433
|
+
return void 0;
|
|
27434
|
+
}
|
|
27435
|
+
}
|
|
27436
|
+
var UNRETRYABLE_SERVICE_CODES = /* @__PURE__ */ new Set([
|
|
27437
|
+
"daily_turn_budget_exhausted",
|
|
27438
|
+
"daily_session_budget_exhausted"
|
|
27439
|
+
]);
|
|
27401
27440
|
var AssistantClientError = class extends Error {
|
|
27402
27441
|
detail;
|
|
27403
27442
|
constructor(detail, message, options) {
|
|
@@ -27407,7 +27446,7 @@ var AssistantClientError = class extends Error {
|
|
|
27407
27446
|
}
|
|
27408
27447
|
};
|
|
27409
27448
|
var DefaultAssistantClient = class {
|
|
27410
|
-
#
|
|
27449
|
+
#source;
|
|
27411
27450
|
#fetch;
|
|
27412
27451
|
#listeners = /* @__PURE__ */ new Set();
|
|
27413
27452
|
#chat = new AssistantChatStateStore();
|
|
@@ -27417,9 +27456,9 @@ var DefaultAssistantClient = class {
|
|
|
27417
27456
|
#context;
|
|
27418
27457
|
#modelContext;
|
|
27419
27458
|
#active;
|
|
27459
|
+
#pendingAppInteractions = /* @__PURE__ */ new Map();
|
|
27420
27460
|
constructor(options) {
|
|
27421
|
-
|
|
27422
|
-
this.#sessionEndpoint = options.sessionEndpoint;
|
|
27461
|
+
this.#source = resolveSessionSource(options);
|
|
27423
27462
|
this.#fetch = options.fetch ?? ((input, init) => globalThis.fetch(input, init));
|
|
27424
27463
|
this.#context = options.context ? { ...options.context } : void 0;
|
|
27425
27464
|
this.#modelContext = options.modelContext ? copyAssistantModelContext(options.modelContext) : void 0;
|
|
@@ -27458,6 +27497,7 @@ var DefaultAssistantClient = class {
|
|
|
27458
27497
|
}
|
|
27459
27498
|
await this.#singleFlight(async (signal) => {
|
|
27460
27499
|
await this.#runChatOperation(signal, async () => {
|
|
27500
|
+
const pendingApp = this.#pendingAppInteractions.get(id);
|
|
27461
27501
|
const session = this.#session;
|
|
27462
27502
|
if (!session) {
|
|
27463
27503
|
throw clientError("confirmation_expired", "assistant session is not active", false);
|
|
@@ -27477,32 +27517,67 @@ var DefaultAssistantClient = class {
|
|
|
27477
27517
|
...resolution.action === "accept" && resolution.content !== void 0 ? { content: resolution.content } : {}
|
|
27478
27518
|
} : { id };
|
|
27479
27519
|
this.#emit({ event: "interaction_started", data: { id, action: resolution.action } });
|
|
27480
|
-
|
|
27481
|
-
|
|
27482
|
-
|
|
27483
|
-
|
|
27484
|
-
|
|
27485
|
-
|
|
27486
|
-
|
|
27487
|
-
|
|
27488
|
-
|
|
27489
|
-
|
|
27490
|
-
if (response.status === 401) {
|
|
27491
|
-
this.#session = void 0;
|
|
27492
|
-
this.#emit({ event: "session_expired", data: {} });
|
|
27493
|
-
}
|
|
27494
|
-
if (!response.ok) {
|
|
27495
|
-
const serverCode = await readStableErrorCode(response);
|
|
27496
|
-
const expired = response.status === 401 || response.status === 409;
|
|
27497
|
-
throw clientError(
|
|
27498
|
-
serverCode ?? (expired ? "confirmation_expired" : "confirmation_failed"),
|
|
27499
|
-
`Assistant interaction failed (${response.status})`,
|
|
27500
|
-
false,
|
|
27501
|
-
response.status
|
|
27520
|
+
try {
|
|
27521
|
+
const response = await this.#request(
|
|
27522
|
+
endpoint,
|
|
27523
|
+
{
|
|
27524
|
+
method: "POST",
|
|
27525
|
+
headers: authorizedHeaders(session.token, "text/event-stream"),
|
|
27526
|
+
body: JSON.stringify(body),
|
|
27527
|
+
signal
|
|
27528
|
+
},
|
|
27529
|
+
"confirmation_failed"
|
|
27502
27530
|
);
|
|
27531
|
+
if (response.status === 401) {
|
|
27532
|
+
this.#session = void 0;
|
|
27533
|
+
this.#emit({ event: "session_expired", data: {} });
|
|
27534
|
+
}
|
|
27535
|
+
if (!response.ok) {
|
|
27536
|
+
const serverCode = await readStableErrorCode(response);
|
|
27537
|
+
const expired = response.status === 401 || response.status === 409;
|
|
27538
|
+
throw clientError(
|
|
27539
|
+
serverCode ?? (expired ? "confirmation_expired" : "confirmation_failed"),
|
|
27540
|
+
`Assistant interaction failed (${response.status})`,
|
|
27541
|
+
false,
|
|
27542
|
+
response.status
|
|
27543
|
+
);
|
|
27544
|
+
}
|
|
27545
|
+
let completedResult;
|
|
27546
|
+
let nextInteractionId;
|
|
27547
|
+
await this.#consume(response, (event) => {
|
|
27548
|
+
if (event.event === "tool_completed" && event.data.id === id) {
|
|
27549
|
+
completedResult = event.data.result;
|
|
27550
|
+
}
|
|
27551
|
+
if ((event.event === "tool_proposed" || event.event === "input_requested") && event.data.id !== id) {
|
|
27552
|
+
nextInteractionId = event.data.id;
|
|
27553
|
+
}
|
|
27554
|
+
});
|
|
27555
|
+
this.#emit({ event: "interaction_completed", data: { id, action: resolution.action } });
|
|
27556
|
+
if (pendingApp) {
|
|
27557
|
+
this.#pendingAppInteractions.delete(id);
|
|
27558
|
+
if (nextInteractionId) {
|
|
27559
|
+
this.#pendingAppInteractions.set(nextInteractionId, pendingApp);
|
|
27560
|
+
} else if (resolution.action !== "accept") {
|
|
27561
|
+
pendingApp.resolve(appInteractionStopped(resolution.action));
|
|
27562
|
+
} else if (completedResult !== void 0) {
|
|
27563
|
+
pendingApp.resolve(appToolResult(completedResult));
|
|
27564
|
+
} else {
|
|
27565
|
+
pendingApp.reject(
|
|
27566
|
+
clientError(
|
|
27567
|
+
"invalid_response",
|
|
27568
|
+
"Assistant app interaction completed without a tool result",
|
|
27569
|
+
false
|
|
27570
|
+
)
|
|
27571
|
+
);
|
|
27572
|
+
}
|
|
27573
|
+
}
|
|
27574
|
+
} catch (error51) {
|
|
27575
|
+
if (pendingApp && this.#pendingAppInteractions.get(id) === pendingApp) {
|
|
27576
|
+
this.#pendingAppInteractions.delete(id);
|
|
27577
|
+
pendingApp.reject(error51);
|
|
27578
|
+
}
|
|
27579
|
+
throw error51;
|
|
27503
27580
|
}
|
|
27504
|
-
await this.#consume(response);
|
|
27505
|
-
this.#emit({ event: "interaction_completed", data: { id, action: resolution.action } });
|
|
27506
27581
|
});
|
|
27507
27582
|
});
|
|
27508
27583
|
}
|
|
@@ -27543,12 +27618,29 @@ var DefaultAssistantClient = class {
|
|
|
27543
27618
|
response.status
|
|
27544
27619
|
);
|
|
27545
27620
|
}
|
|
27546
|
-
|
|
27621
|
+
const value = await response.json();
|
|
27622
|
+
const interaction = parseAppInteraction(value);
|
|
27623
|
+
if (!interaction) return value;
|
|
27624
|
+
return new Promise((resolve2, reject) => {
|
|
27625
|
+
const id = interaction.data.id;
|
|
27626
|
+
const replaced = this.#pendingAppInteractions.get(id);
|
|
27627
|
+
if (replaced) {
|
|
27628
|
+
replaced.reject(
|
|
27629
|
+
clientError("invalid_response", "Assistant returned a duplicate app interaction", false)
|
|
27630
|
+
);
|
|
27631
|
+
}
|
|
27632
|
+
this.#pendingAppInteractions.set(id, { resolve: resolve2, reject });
|
|
27633
|
+
this.#emit(interaction);
|
|
27634
|
+
void this.#chat.flush();
|
|
27635
|
+
});
|
|
27547
27636
|
}
|
|
27548
27637
|
appSandboxUrl() {
|
|
27549
27638
|
return this.#session?.endpoints.sandbox;
|
|
27550
27639
|
}
|
|
27551
27640
|
resetSession() {
|
|
27641
|
+
this.#rejectPendingAppInteractions(
|
|
27642
|
+
clientError("confirmation_expired", "assistant session is not active", false)
|
|
27643
|
+
);
|
|
27552
27644
|
this.#session = void 0;
|
|
27553
27645
|
this.#emit({ event: "session_reset", data: {} });
|
|
27554
27646
|
}
|
|
@@ -27582,11 +27674,14 @@ var DefaultAssistantClient = class {
|
|
|
27582
27674
|
return;
|
|
27583
27675
|
}
|
|
27584
27676
|
if (!response.ok) {
|
|
27677
|
+
const serviceCode = await refusalCode(response);
|
|
27585
27678
|
throw clientError(
|
|
27586
27679
|
"turn_failed",
|
|
27587
27680
|
`Assistant turn failed (${response.status})`,
|
|
27588
27681
|
false,
|
|
27589
|
-
response.status
|
|
27682
|
+
response.status,
|
|
27683
|
+
void 0,
|
|
27684
|
+
serviceCode
|
|
27590
27685
|
);
|
|
27591
27686
|
}
|
|
27592
27687
|
await this.#consume(response);
|
|
@@ -27625,23 +27720,25 @@ var DefaultAssistantClient = class {
|
|
|
27625
27720
|
}
|
|
27626
27721
|
}
|
|
27627
27722
|
async #createSession(signal) {
|
|
27628
|
-
const
|
|
27629
|
-
|
|
27630
|
-
|
|
27631
|
-
|
|
27632
|
-
|
|
27633
|
-
|
|
27634
|
-
|
|
27635
|
-
|
|
27636
|
-
|
|
27637
|
-
|
|
27638
|
-
);
|
|
27723
|
+
const source = this.#source;
|
|
27724
|
+
const response = await this.#requestSession(source, {
|
|
27725
|
+
method: "POST",
|
|
27726
|
+
headers: { Accept: "application/json", "Content-Type": "application/json" },
|
|
27727
|
+
body: JSON.stringify(
|
|
27728
|
+
source.kind === "public" ? { embedId: source.embedId } : this.#context ? { context: this.#context } : {}
|
|
27729
|
+
),
|
|
27730
|
+
credentials: source.kind === "public" ? "omit" : "same-origin",
|
|
27731
|
+
signal
|
|
27732
|
+
});
|
|
27639
27733
|
if (!response.ok) {
|
|
27734
|
+
const serviceCode = await refusalCode(response);
|
|
27640
27735
|
throw clientError(
|
|
27641
27736
|
"session_failed",
|
|
27642
27737
|
`Assistant session failed (${response.status})`,
|
|
27643
|
-
|
|
27644
|
-
response.status
|
|
27738
|
+
serviceCode === void 0 || !UNRETRYABLE_SERVICE_CODES.has(serviceCode),
|
|
27739
|
+
response.status,
|
|
27740
|
+
void 0,
|
|
27741
|
+
serviceCode
|
|
27645
27742
|
);
|
|
27646
27743
|
}
|
|
27647
27744
|
let value;
|
|
@@ -27669,12 +27766,13 @@ var DefaultAssistantClient = class {
|
|
|
27669
27766
|
});
|
|
27670
27767
|
return session;
|
|
27671
27768
|
}
|
|
27672
|
-
async #consume(response) {
|
|
27769
|
+
async #consume(response, observe) {
|
|
27673
27770
|
let streamError;
|
|
27674
27771
|
try {
|
|
27675
27772
|
await consumeAssistantEvents(response, (event) => {
|
|
27676
27773
|
const clientEvent = toAssistantClientEvent(event);
|
|
27677
27774
|
this.#emit(clientEvent);
|
|
27775
|
+
observe?.(clientEvent);
|
|
27678
27776
|
if (clientEvent.event !== "error" || typeof clientEvent.data.code !== "string") return;
|
|
27679
27777
|
streamError = {
|
|
27680
27778
|
code: clientEvent.data.code,
|
|
@@ -27697,6 +27795,34 @@ var DefaultAssistantClient = class {
|
|
|
27697
27795
|
);
|
|
27698
27796
|
}
|
|
27699
27797
|
}
|
|
27798
|
+
/**
|
|
27799
|
+
* The mint, with the one failure a public page hits that an in-app embed cannot.
|
|
27800
|
+
*
|
|
27801
|
+
* A page that loads the embed script and then blocks `connect-src` fails here as a bare network
|
|
27802
|
+
* rejection — no status, no body, nothing to read. "Assistant request failed" sends a developer
|
|
27803
|
+
* hunting through their own code; naming the directive and the origin ends the search. The message
|
|
27804
|
+
* covers a plain outage too, because from inside the browser the two are indistinguishable.
|
|
27805
|
+
*/
|
|
27806
|
+
async #requestSession(source, init) {
|
|
27807
|
+
try {
|
|
27808
|
+
return await this.#fetch(source.url, init);
|
|
27809
|
+
} catch (error51) {
|
|
27810
|
+
if (init.signal?.aborted) throw error51;
|
|
27811
|
+
if (source.kind !== "public") {
|
|
27812
|
+
throw clientError("session_failed", "Assistant request failed", true, void 0, {
|
|
27813
|
+
cause: error51
|
|
27814
|
+
});
|
|
27815
|
+
}
|
|
27816
|
+
throw clientError(
|
|
27817
|
+
"session_failed",
|
|
27818
|
+
`Assistant could not reach ${new URL(source.url).origin}. If the page sets a Content-Security-Policy, allow that origin in connect-src (and in script-src and frame-src).`,
|
|
27819
|
+
true,
|
|
27820
|
+
void 0,
|
|
27821
|
+
{ cause: error51 },
|
|
27822
|
+
"blocked_by_page"
|
|
27823
|
+
);
|
|
27824
|
+
}
|
|
27825
|
+
}
|
|
27700
27826
|
async #request(input, init, failureCode) {
|
|
27701
27827
|
try {
|
|
27702
27828
|
return await this.#fetch(input, init);
|
|
@@ -27743,6 +27869,10 @@ var DefaultAssistantClient = class {
|
|
|
27743
27869
|
}
|
|
27744
27870
|
}
|
|
27745
27871
|
}
|
|
27872
|
+
#rejectPendingAppInteractions(error51) {
|
|
27873
|
+
for (const pending of this.#pendingAppInteractions.values()) pending.reject(error51);
|
|
27874
|
+
this.#pendingAppInteractions.clear();
|
|
27875
|
+
}
|
|
27746
27876
|
};
|
|
27747
27877
|
function createAssistantClient(options) {
|
|
27748
27878
|
return new DefaultAssistantClient(options);
|
|
@@ -27754,6 +27884,30 @@ function authorizedHeaders(token, accept) {
|
|
|
27754
27884
|
Accept: accept
|
|
27755
27885
|
};
|
|
27756
27886
|
}
|
|
27887
|
+
function parseAppInteraction(value) {
|
|
27888
|
+
if (!isRecord4(value) || !isRecord4(value.interaction)) return void 0;
|
|
27889
|
+
const event = value.interaction.event;
|
|
27890
|
+
const data = value.interaction.data;
|
|
27891
|
+
if (event !== "tool_proposed" && event !== "input_requested" || !isRecord4(data)) {
|
|
27892
|
+
return void 0;
|
|
27893
|
+
}
|
|
27894
|
+
const parsed = toAssistantClientEvent({ event, data });
|
|
27895
|
+
return parsed.event === "tool_proposed" || parsed.event === "input_requested" ? parsed : void 0;
|
|
27896
|
+
}
|
|
27897
|
+
function appToolResult(result) {
|
|
27898
|
+
const text3 = typeof result === "string" ? result : JSON.stringify(result);
|
|
27899
|
+
return result !== null && typeof result === "object" && !Array.isArray(result) ? {
|
|
27900
|
+
content: [{ type: "text", text: text3 }],
|
|
27901
|
+
structuredContent: result,
|
|
27902
|
+
isError: false
|
|
27903
|
+
} : { content: [{ type: "text", text: text3 }], isError: false };
|
|
27904
|
+
}
|
|
27905
|
+
function appInteractionStopped(action) {
|
|
27906
|
+
return {
|
|
27907
|
+
content: [{ type: "text", text: `interaction_${action}` }],
|
|
27908
|
+
isError: true
|
|
27909
|
+
};
|
|
27910
|
+
}
|
|
27757
27911
|
function parseSession(value) {
|
|
27758
27912
|
if (!isRecord4(value) || !isRecord4(value.endpoints)) {
|
|
27759
27913
|
throw clientError("session_failed", "Assistant session response is invalid", true);
|
|
@@ -27790,9 +27944,14 @@ async function readStableErrorCode(response) {
|
|
|
27790
27944
|
if (!isRecord4(value) || typeof value.code !== "string") return void 0;
|
|
27791
27945
|
return /^[A-Za-z0-9_.-]{1,64}$/.test(value.code) ? value.code : void 0;
|
|
27792
27946
|
}
|
|
27793
|
-
function clientError(code, message, retryable, status, options) {
|
|
27947
|
+
function clientError(code, message, retryable, status, options, serviceCode) {
|
|
27794
27948
|
return new AssistantClientError(
|
|
27795
|
-
{
|
|
27949
|
+
{
|
|
27950
|
+
code,
|
|
27951
|
+
...status === void 0 ? {} : { status },
|
|
27952
|
+
retryable,
|
|
27953
|
+
...serviceCode === void 0 ? {} : { serviceCode }
|
|
27954
|
+
},
|
|
27796
27955
|
message,
|
|
27797
27956
|
options
|
|
27798
27957
|
);
|
|
@@ -30969,7 +31128,6 @@ var AssistantElementEventController = class {
|
|
|
30969
31128
|
return;
|
|
30970
31129
|
}
|
|
30971
31130
|
if (name21 === "tool_proposed" || name21 === "interaction_proposed") {
|
|
30972
|
-
if (!this.#streamKind) return;
|
|
30973
31131
|
this.#appendToolProposal(
|
|
30974
31132
|
String(data.id ?? ""),
|
|
30975
31133
|
String(data.tool ?? "action"),
|
|
@@ -30981,7 +31139,6 @@ var AssistantElementEventController = class {
|
|
|
30981
31139
|
return;
|
|
30982
31140
|
}
|
|
30983
31141
|
if (name21 === "input_requested" && isRecord5(data.requestedSchema)) {
|
|
30984
|
-
if (!this.#streamKind) return;
|
|
30985
31142
|
this.#appendInputRequest(
|
|
30986
31143
|
String(data.id ?? ""),
|
|
30987
31144
|
String(data.message ?? "More information is required"),
|
|
@@ -31885,6 +32042,10 @@ var NoodleAssistantElement = class extends HTMLElementBase {
|
|
|
31885
32042
|
#appearanceStyleSnapshot = /* @__PURE__ */ new Map();
|
|
31886
32043
|
#scroll = new AssistantElementScrollController();
|
|
31887
32044
|
sessionEndpoint = "";
|
|
32045
|
+
/** Public mount: the non-secret embed id `noodle deploy` printed. Mutually exclusive with the above. */
|
|
32046
|
+
embedId = "";
|
|
32047
|
+
/** Only for a dev or self-hosted service; the published snippet needs no origin. */
|
|
32048
|
+
serviceUrl = "";
|
|
31888
32049
|
get appearance() {
|
|
31889
32050
|
return this.#hostAppearance;
|
|
31890
32051
|
}
|
|
@@ -31939,6 +32100,8 @@ var NoodleAssistantElement = class extends HTMLElementBase {
|
|
|
31939
32100
|
connectedCallback() {
|
|
31940
32101
|
if (!this.shadowRoot) this.attachShadow({ mode: "open" });
|
|
31941
32102
|
if (!this.sessionEndpoint) this.sessionEndpoint = this.getAttribute("session-endpoint") ?? "";
|
|
32103
|
+
if (!this.embedId) this.embedId = this.getAttribute("embed-id") ?? "";
|
|
32104
|
+
if (!this.serviceUrl) this.serviceUrl = this.getAttribute("service-url") ?? "";
|
|
31942
32105
|
this.#media = globalThis.matchMedia?.("(prefers-color-scheme: dark)");
|
|
31943
32106
|
this.#media?.addEventListener("change", this.#handleSystemTheme);
|
|
31944
32107
|
document.addEventListener("keydown", this.#handleDocumentKeydown);
|
|
@@ -32110,26 +32273,31 @@ var NoodleAssistantElement = class extends HTMLElementBase {
|
|
|
32110
32273
|
}
|
|
32111
32274
|
};
|
|
32112
32275
|
#ensureClient() {
|
|
32113
|
-
|
|
32276
|
+
const key = sessionSourceKey(this);
|
|
32277
|
+
if (this.#client && this.#clientEndpoint === key) return this.#client;
|
|
32114
32278
|
this.#client?.abort();
|
|
32115
32279
|
this.#unsubscribeClient?.();
|
|
32116
32280
|
this.#sessionBootstrap = void 0;
|
|
32117
|
-
const
|
|
32118
|
-
sessionEndpoint: this.sessionEndpoint,
|
|
32281
|
+
const behavior = {
|
|
32119
32282
|
...this.#context ? { context: this.#context } : {},
|
|
32120
32283
|
clientContext: browserClientContext,
|
|
32121
32284
|
...this.#pageContext ? { pageContext: this.#pageContext } : {},
|
|
32122
32285
|
...this.#modelContext ? { modelContext: this.#modelContext } : {}
|
|
32123
|
-
}
|
|
32286
|
+
};
|
|
32287
|
+
const client = this.embedId ? createAssistantClient({
|
|
32288
|
+
embedId: this.embedId,
|
|
32289
|
+
...this.serviceUrl ? { serviceUrl: this.serviceUrl } : {},
|
|
32290
|
+
...behavior
|
|
32291
|
+
}) : createAssistantClient({ sessionEndpoint: this.sessionEndpoint, ...behavior });
|
|
32124
32292
|
this.#client = client;
|
|
32125
|
-
this.#clientEndpoint =
|
|
32293
|
+
this.#clientEndpoint = key;
|
|
32126
32294
|
this.#unsubscribeClient = client.subscribe((event) => {
|
|
32127
32295
|
if (this.#client === client) this.#handleClientEvent(event);
|
|
32128
32296
|
});
|
|
32129
32297
|
return client;
|
|
32130
32298
|
}
|
|
32131
32299
|
#primeSession() {
|
|
32132
|
-
if (!this.sessionEndpoint) return;
|
|
32300
|
+
if (!this.sessionEndpoint && !this.embedId) return;
|
|
32133
32301
|
const client = this.#ensureClient();
|
|
32134
32302
|
const generation = this.#conversationGeneration;
|
|
32135
32303
|
this.#setSessionState("loading");
|
|
@@ -32253,23 +32421,30 @@ var NoodleAssistantElement = class extends HTMLElementBase {
|
|
|
32253
32421
|
}
|
|
32254
32422
|
const detail = error51 instanceof AssistantClientError ? error51.detail : { code: fallbackCode, retryable: false };
|
|
32255
32423
|
this.#appendRecoveryError(
|
|
32256
|
-
detail.code === "session_expired" ? this.#appearance.labels.sessionExpired : this.#appearance.labels.unavailable
|
|
32424
|
+
detail.code === "session_expired" ? this.#appearance.labels.sessionExpired : this.#appearance.labels.unavailable,
|
|
32425
|
+
// A spent or switched-off daily budget offers no retry. Reconnecting cannot succeed, and every
|
|
32426
|
+
// open tab trying is exactly the load the cap was set to refuse — so the button goes, not just
|
|
32427
|
+
// the alarming words. A spent *session* keeps it: reconnect mints a fresh one, which is the fix.
|
|
32428
|
+
detail.retryable !== false || detail.serviceCode === void 0
|
|
32257
32429
|
);
|
|
32258
32430
|
this.#dispatchError(detail);
|
|
32259
32431
|
}
|
|
32260
|
-
#appendRecoveryError(message) {
|
|
32432
|
+
#appendRecoveryError(message, offerRetry = true) {
|
|
32261
32433
|
if (!this.#messages) return;
|
|
32262
32434
|
this.#messages.querySelector(".conversation-error")?.remove();
|
|
32263
32435
|
const error51 = document.createElement("section");
|
|
32264
32436
|
error51.className = "conversation-error";
|
|
32265
|
-
error51.setAttribute("role", "alert");
|
|
32437
|
+
error51.setAttribute("role", offerRetry ? "alert" : "status");
|
|
32266
32438
|
const text3 = document.createElement("p");
|
|
32267
32439
|
text3.textContent = message;
|
|
32268
|
-
|
|
32269
|
-
|
|
32270
|
-
|
|
32271
|
-
|
|
32272
|
-
|
|
32440
|
+
error51.append(text3);
|
|
32441
|
+
if (offerRetry) {
|
|
32442
|
+
const retry = document.createElement("button");
|
|
32443
|
+
retry.type = "button";
|
|
32444
|
+
retry.textContent = this.#appearance.labels.reconnect;
|
|
32445
|
+
retry.addEventListener("click", () => this.reconnect());
|
|
32446
|
+
error51.append(retry);
|
|
32447
|
+
}
|
|
32273
32448
|
this.#messages.append(error51);
|
|
32274
32449
|
this.#revealLatest();
|
|
32275
32450
|
}
|
|
@@ -32547,7 +32722,9 @@ var NoodleAssistantWithRef = (0, import_react2.forwardRef)(
|
|
|
32547
32722
|
element.addEventListener("assistant-event", assistantEvent);
|
|
32548
32723
|
element.addEventListener("assistant-appearance-warning", appearanceWarning);
|
|
32549
32724
|
registerNoodleAssistant();
|
|
32550
|
-
element.sessionEndpoint = props.sessionEndpoint;
|
|
32725
|
+
element.sessionEndpoint = props.sessionEndpoint ?? "";
|
|
32726
|
+
element.embedId = props.embedId ?? "";
|
|
32727
|
+
element.serviceUrl = props.serviceUrl ?? "";
|
|
32551
32728
|
element.appearance = props.appearance;
|
|
32552
32729
|
element.toggleAttribute("open", props.open === true);
|
|
32553
32730
|
if (props.context) element.updateContext(props.context);
|
|
@@ -32572,12 +32749,16 @@ var NoodleAssistantWithRef = (0, import_react2.forwardRef)(
|
|
|
32572
32749
|
props.onSessionExpired,
|
|
32573
32750
|
props.open,
|
|
32574
32751
|
props.pageContext,
|
|
32575
|
-
props.sessionEndpoint
|
|
32752
|
+
props.sessionEndpoint,
|
|
32753
|
+
props.embedId,
|
|
32754
|
+
props.serviceUrl
|
|
32576
32755
|
]);
|
|
32577
32756
|
return (0, import_react2.createElement)("noodle-assistant", {
|
|
32578
32757
|
ref,
|
|
32579
32758
|
class: props.className,
|
|
32580
|
-
"session-endpoint": props.sessionEndpoint,
|
|
32759
|
+
...props.sessionEndpoint ? { "session-endpoint": props.sessionEndpoint } : {},
|
|
32760
|
+
...props.embedId ? { "embed-id": props.embedId } : {},
|
|
32761
|
+
...props.serviceUrl ? { "service-url": props.serviceUrl } : {},
|
|
32581
32762
|
...props.theme ? { theme: props.theme } : {}
|
|
32582
32763
|
});
|
|
32583
32764
|
}
|