@noodleseed/assistant 1.14.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/dist/{chunk-RITHSCTD.js → chunk-RRJIK473.js} +89 -20
- package/dist/chunk-RRJIK473.js.map +1 -0
- package/dist/{chunk-7WXGZJCA.js → chunk-SUHNNWKP.js} +36 -17
- package/dist/{chunk-7WXGZJCA.js.map → chunk-SUHNNWKP.js.map} +1 -1
- 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 +84 -19
- 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 +119 -33
- 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 +95 -23
- 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 +128 -36
- 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-RITHSCTD.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
|
@@ -27279,6 +27279,24 @@ function hasToJsonProperty(value) {
|
|
|
27279
27279
|
return false;
|
|
27280
27280
|
}
|
|
27281
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
|
+
|
|
27282
27300
|
// src/transport.ts
|
|
27283
27301
|
var AssistantTransportError = class extends Error {
|
|
27284
27302
|
code = "invalid_response";
|
|
@@ -27406,6 +27424,19 @@ function isAbortError2(error51) {
|
|
|
27406
27424
|
}
|
|
27407
27425
|
|
|
27408
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
|
+
]);
|
|
27409
27440
|
var AssistantClientError = class extends Error {
|
|
27410
27441
|
detail;
|
|
27411
27442
|
constructor(detail, message, options) {
|
|
@@ -27415,7 +27446,7 @@ var AssistantClientError = class extends Error {
|
|
|
27415
27446
|
}
|
|
27416
27447
|
};
|
|
27417
27448
|
var DefaultAssistantClient = class {
|
|
27418
|
-
#
|
|
27449
|
+
#source;
|
|
27419
27450
|
#fetch;
|
|
27420
27451
|
#listeners = /* @__PURE__ */ new Set();
|
|
27421
27452
|
#chat = new AssistantChatStateStore();
|
|
@@ -27427,8 +27458,7 @@ var DefaultAssistantClient = class {
|
|
|
27427
27458
|
#active;
|
|
27428
27459
|
#pendingAppInteractions = /* @__PURE__ */ new Map();
|
|
27429
27460
|
constructor(options) {
|
|
27430
|
-
|
|
27431
|
-
this.#sessionEndpoint = options.sessionEndpoint;
|
|
27461
|
+
this.#source = resolveSessionSource(options);
|
|
27432
27462
|
this.#fetch = options.fetch ?? ((input, init) => globalThis.fetch(input, init));
|
|
27433
27463
|
this.#context = options.context ? { ...options.context } : void 0;
|
|
27434
27464
|
this.#modelContext = options.modelContext ? copyAssistantModelContext(options.modelContext) : void 0;
|
|
@@ -27644,11 +27674,14 @@ var DefaultAssistantClient = class {
|
|
|
27644
27674
|
return;
|
|
27645
27675
|
}
|
|
27646
27676
|
if (!response.ok) {
|
|
27677
|
+
const serviceCode = await refusalCode(response);
|
|
27647
27678
|
throw clientError(
|
|
27648
27679
|
"turn_failed",
|
|
27649
27680
|
`Assistant turn failed (${response.status})`,
|
|
27650
27681
|
false,
|
|
27651
|
-
response.status
|
|
27682
|
+
response.status,
|
|
27683
|
+
void 0,
|
|
27684
|
+
serviceCode
|
|
27652
27685
|
);
|
|
27653
27686
|
}
|
|
27654
27687
|
await this.#consume(response);
|
|
@@ -27687,23 +27720,25 @@ var DefaultAssistantClient = class {
|
|
|
27687
27720
|
}
|
|
27688
27721
|
}
|
|
27689
27722
|
async #createSession(signal) {
|
|
27690
|
-
const
|
|
27691
|
-
|
|
27692
|
-
|
|
27693
|
-
|
|
27694
|
-
|
|
27695
|
-
|
|
27696
|
-
|
|
27697
|
-
|
|
27698
|
-
|
|
27699
|
-
|
|
27700
|
-
);
|
|
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
|
+
});
|
|
27701
27733
|
if (!response.ok) {
|
|
27734
|
+
const serviceCode = await refusalCode(response);
|
|
27702
27735
|
throw clientError(
|
|
27703
27736
|
"session_failed",
|
|
27704
27737
|
`Assistant session failed (${response.status})`,
|
|
27705
|
-
|
|
27706
|
-
response.status
|
|
27738
|
+
serviceCode === void 0 || !UNRETRYABLE_SERVICE_CODES.has(serviceCode),
|
|
27739
|
+
response.status,
|
|
27740
|
+
void 0,
|
|
27741
|
+
serviceCode
|
|
27707
27742
|
);
|
|
27708
27743
|
}
|
|
27709
27744
|
let value;
|
|
@@ -27760,6 +27795,34 @@ var DefaultAssistantClient = class {
|
|
|
27760
27795
|
);
|
|
27761
27796
|
}
|
|
27762
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
|
+
}
|
|
27763
27826
|
async #request(input, init, failureCode) {
|
|
27764
27827
|
try {
|
|
27765
27828
|
return await this.#fetch(input, init);
|
|
@@ -27881,9 +27944,14 @@ async function readStableErrorCode(response) {
|
|
|
27881
27944
|
if (!isRecord4(value) || typeof value.code !== "string") return void 0;
|
|
27882
27945
|
return /^[A-Za-z0-9_.-]{1,64}$/.test(value.code) ? value.code : void 0;
|
|
27883
27946
|
}
|
|
27884
|
-
function clientError(code, message, retryable, status, options) {
|
|
27947
|
+
function clientError(code, message, retryable, status, options, serviceCode) {
|
|
27885
27948
|
return new AssistantClientError(
|
|
27886
|
-
{
|
|
27949
|
+
{
|
|
27950
|
+
code,
|
|
27951
|
+
...status === void 0 ? {} : { status },
|
|
27952
|
+
retryable,
|
|
27953
|
+
...serviceCode === void 0 ? {} : { serviceCode }
|
|
27954
|
+
},
|
|
27887
27955
|
message,
|
|
27888
27956
|
options
|
|
27889
27957
|
);
|
|
@@ -31974,6 +32042,10 @@ var NoodleAssistantElement = class extends HTMLElementBase {
|
|
|
31974
32042
|
#appearanceStyleSnapshot = /* @__PURE__ */ new Map();
|
|
31975
32043
|
#scroll = new AssistantElementScrollController();
|
|
31976
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 = "";
|
|
31977
32049
|
get appearance() {
|
|
31978
32050
|
return this.#hostAppearance;
|
|
31979
32051
|
}
|
|
@@ -32028,6 +32100,8 @@ var NoodleAssistantElement = class extends HTMLElementBase {
|
|
|
32028
32100
|
connectedCallback() {
|
|
32029
32101
|
if (!this.shadowRoot) this.attachShadow({ mode: "open" });
|
|
32030
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") ?? "";
|
|
32031
32105
|
this.#media = globalThis.matchMedia?.("(prefers-color-scheme: dark)");
|
|
32032
32106
|
this.#media?.addEventListener("change", this.#handleSystemTheme);
|
|
32033
32107
|
document.addEventListener("keydown", this.#handleDocumentKeydown);
|
|
@@ -32199,26 +32273,31 @@ var NoodleAssistantElement = class extends HTMLElementBase {
|
|
|
32199
32273
|
}
|
|
32200
32274
|
};
|
|
32201
32275
|
#ensureClient() {
|
|
32202
|
-
|
|
32276
|
+
const key = sessionSourceKey(this);
|
|
32277
|
+
if (this.#client && this.#clientEndpoint === key) return this.#client;
|
|
32203
32278
|
this.#client?.abort();
|
|
32204
32279
|
this.#unsubscribeClient?.();
|
|
32205
32280
|
this.#sessionBootstrap = void 0;
|
|
32206
|
-
const
|
|
32207
|
-
sessionEndpoint: this.sessionEndpoint,
|
|
32281
|
+
const behavior = {
|
|
32208
32282
|
...this.#context ? { context: this.#context } : {},
|
|
32209
32283
|
clientContext: browserClientContext,
|
|
32210
32284
|
...this.#pageContext ? { pageContext: this.#pageContext } : {},
|
|
32211
32285
|
...this.#modelContext ? { modelContext: this.#modelContext } : {}
|
|
32212
|
-
}
|
|
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 });
|
|
32213
32292
|
this.#client = client;
|
|
32214
|
-
this.#clientEndpoint =
|
|
32293
|
+
this.#clientEndpoint = key;
|
|
32215
32294
|
this.#unsubscribeClient = client.subscribe((event) => {
|
|
32216
32295
|
if (this.#client === client) this.#handleClientEvent(event);
|
|
32217
32296
|
});
|
|
32218
32297
|
return client;
|
|
32219
32298
|
}
|
|
32220
32299
|
#primeSession() {
|
|
32221
|
-
if (!this.sessionEndpoint) return;
|
|
32300
|
+
if (!this.sessionEndpoint && !this.embedId) return;
|
|
32222
32301
|
const client = this.#ensureClient();
|
|
32223
32302
|
const generation = this.#conversationGeneration;
|
|
32224
32303
|
this.#setSessionState("loading");
|
|
@@ -32342,23 +32421,30 @@ var NoodleAssistantElement = class extends HTMLElementBase {
|
|
|
32342
32421
|
}
|
|
32343
32422
|
const detail = error51 instanceof AssistantClientError ? error51.detail : { code: fallbackCode, retryable: false };
|
|
32344
32423
|
this.#appendRecoveryError(
|
|
32345
|
-
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
|
|
32346
32429
|
);
|
|
32347
32430
|
this.#dispatchError(detail);
|
|
32348
32431
|
}
|
|
32349
|
-
#appendRecoveryError(message) {
|
|
32432
|
+
#appendRecoveryError(message, offerRetry = true) {
|
|
32350
32433
|
if (!this.#messages) return;
|
|
32351
32434
|
this.#messages.querySelector(".conversation-error")?.remove();
|
|
32352
32435
|
const error51 = document.createElement("section");
|
|
32353
32436
|
error51.className = "conversation-error";
|
|
32354
|
-
error51.setAttribute("role", "alert");
|
|
32437
|
+
error51.setAttribute("role", offerRetry ? "alert" : "status");
|
|
32355
32438
|
const text3 = document.createElement("p");
|
|
32356
32439
|
text3.textContent = message;
|
|
32357
|
-
|
|
32358
|
-
|
|
32359
|
-
|
|
32360
|
-
|
|
32361
|
-
|
|
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
|
+
}
|
|
32362
32448
|
this.#messages.append(error51);
|
|
32363
32449
|
this.#revealLatest();
|
|
32364
32450
|
}
|
|
@@ -32636,7 +32722,9 @@ var NoodleAssistantWithRef = (0, import_react2.forwardRef)(
|
|
|
32636
32722
|
element.addEventListener("assistant-event", assistantEvent);
|
|
32637
32723
|
element.addEventListener("assistant-appearance-warning", appearanceWarning);
|
|
32638
32724
|
registerNoodleAssistant();
|
|
32639
|
-
element.sessionEndpoint = props.sessionEndpoint;
|
|
32725
|
+
element.sessionEndpoint = props.sessionEndpoint ?? "";
|
|
32726
|
+
element.embedId = props.embedId ?? "";
|
|
32727
|
+
element.serviceUrl = props.serviceUrl ?? "";
|
|
32640
32728
|
element.appearance = props.appearance;
|
|
32641
32729
|
element.toggleAttribute("open", props.open === true);
|
|
32642
32730
|
if (props.context) element.updateContext(props.context);
|
|
@@ -32661,12 +32749,16 @@ var NoodleAssistantWithRef = (0, import_react2.forwardRef)(
|
|
|
32661
32749
|
props.onSessionExpired,
|
|
32662
32750
|
props.open,
|
|
32663
32751
|
props.pageContext,
|
|
32664
|
-
props.sessionEndpoint
|
|
32752
|
+
props.sessionEndpoint,
|
|
32753
|
+
props.embedId,
|
|
32754
|
+
props.serviceUrl
|
|
32665
32755
|
]);
|
|
32666
32756
|
return (0, import_react2.createElement)("noodle-assistant", {
|
|
32667
32757
|
ref,
|
|
32668
32758
|
class: props.className,
|
|
32669
|
-
"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 } : {},
|
|
32670
32762
|
...props.theme ? { theme: props.theme } : {}
|
|
32671
32763
|
});
|
|
32672
32764
|
}
|