@noodleseed/assistant 1.14.0 → 1.16.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-7WXGZJCA.js → chunk-7L52WJ2E.js} +101 -17
- package/dist/chunk-7L52WJ2E.js.map +1 -0
- package/dist/{chunk-RITHSCTD.js → chunk-NPV64PUX.js} +94 -20
- package/dist/chunk-NPV64PUX.js.map +1 -0
- package/dist/{client-BKIeuSVS.d.cts → client-401BAJqk.d.cts} +42 -3
- package/dist/{client-BccAX_om.d.ts → client-Ii_xTV09.d.ts} +42 -3
- package/dist/client.cjs +89 -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 +189 -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 +100 -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 +198 -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-7WXGZJCA.js.map +0 -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-401BAJqk.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-Ii_xTV09.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-NPV64PUX.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
|
@@ -27095,6 +27095,11 @@ function toAssistantClientEvent(event) {
|
|
|
27095
27095
|
return event;
|
|
27096
27096
|
}
|
|
27097
27097
|
break;
|
|
27098
|
+
case "auth_requested":
|
|
27099
|
+
if (hasString(value, "id") && hasString(value, "tool") && hasString(value, "continuation") && hasString(value, "expiresAt") && hasOptionalTurnId(value)) {
|
|
27100
|
+
return event;
|
|
27101
|
+
}
|
|
27102
|
+
break;
|
|
27098
27103
|
case "interaction_resolved":
|
|
27099
27104
|
if (hasString(value, "id") && (value.action === void 0 || isInteractionAction(value.action)) && hasOptionalTurnId(value)) {
|
|
27100
27105
|
return event;
|
|
@@ -27279,6 +27284,24 @@ function hasToJsonProperty(value) {
|
|
|
27279
27284
|
return false;
|
|
27280
27285
|
}
|
|
27281
27286
|
|
|
27287
|
+
// src/session-source.ts
|
|
27288
|
+
var NOODLE_CLOUD_URL = "https://cloud.noodleseed.dev";
|
|
27289
|
+
var PUBLIC_SESSION_PATH = "/v1/assistant/public-sessions";
|
|
27290
|
+
function resolveSessionSource(options) {
|
|
27291
|
+
const { sessionEndpoint, embedId, serviceUrl } = options;
|
|
27292
|
+
if (Boolean(embedId) === Boolean(sessionEndpoint)) {
|
|
27293
|
+
throw new Error("pass either embedId or sessionEndpoint, not both and not neither");
|
|
27294
|
+
}
|
|
27295
|
+
if (embedId) {
|
|
27296
|
+
const base = (serviceUrl ?? NOODLE_CLOUD_URL).replace(/\/+$/, "");
|
|
27297
|
+
return { kind: "public", url: `${base}${PUBLIC_SESSION_PATH}`, embedId };
|
|
27298
|
+
}
|
|
27299
|
+
return { kind: "exchange", url: sessionEndpoint };
|
|
27300
|
+
}
|
|
27301
|
+
function sessionSourceKey(options) {
|
|
27302
|
+
return `${options.embedId ?? ""}|${options.serviceUrl ?? ""}|${options.sessionEndpoint ?? ""}`;
|
|
27303
|
+
}
|
|
27304
|
+
|
|
27282
27305
|
// src/transport.ts
|
|
27283
27306
|
var AssistantTransportError = class extends Error {
|
|
27284
27307
|
code = "invalid_response";
|
|
@@ -27406,6 +27429,19 @@ function isAbortError2(error51) {
|
|
|
27406
27429
|
}
|
|
27407
27430
|
|
|
27408
27431
|
// src/client.ts
|
|
27432
|
+
async function refusalCode(response) {
|
|
27433
|
+
try {
|
|
27434
|
+
const body = await response.clone().json();
|
|
27435
|
+
const code = body.code;
|
|
27436
|
+
return typeof code === "string" ? code : void 0;
|
|
27437
|
+
} catch {
|
|
27438
|
+
return void 0;
|
|
27439
|
+
}
|
|
27440
|
+
}
|
|
27441
|
+
var UNRETRYABLE_SERVICE_CODES = /* @__PURE__ */ new Set([
|
|
27442
|
+
"daily_turn_budget_exhausted",
|
|
27443
|
+
"daily_session_budget_exhausted"
|
|
27444
|
+
]);
|
|
27409
27445
|
var AssistantClientError = class extends Error {
|
|
27410
27446
|
detail;
|
|
27411
27447
|
constructor(detail, message, options) {
|
|
@@ -27415,7 +27451,7 @@ var AssistantClientError = class extends Error {
|
|
|
27415
27451
|
}
|
|
27416
27452
|
};
|
|
27417
27453
|
var DefaultAssistantClient = class {
|
|
27418
|
-
#
|
|
27454
|
+
#source;
|
|
27419
27455
|
#fetch;
|
|
27420
27456
|
#listeners = /* @__PURE__ */ new Set();
|
|
27421
27457
|
#chat = new AssistantChatStateStore();
|
|
@@ -27427,8 +27463,7 @@ var DefaultAssistantClient = class {
|
|
|
27427
27463
|
#active;
|
|
27428
27464
|
#pendingAppInteractions = /* @__PURE__ */ new Map();
|
|
27429
27465
|
constructor(options) {
|
|
27430
|
-
|
|
27431
|
-
this.#sessionEndpoint = options.sessionEndpoint;
|
|
27466
|
+
this.#source = resolveSessionSource(options);
|
|
27432
27467
|
this.#fetch = options.fetch ?? ((input, init) => globalThis.fetch(input, init));
|
|
27433
27468
|
this.#context = options.context ? { ...options.context } : void 0;
|
|
27434
27469
|
this.#modelContext = options.modelContext ? copyAssistantModelContext(options.modelContext) : void 0;
|
|
@@ -27644,11 +27679,14 @@ var DefaultAssistantClient = class {
|
|
|
27644
27679
|
return;
|
|
27645
27680
|
}
|
|
27646
27681
|
if (!response.ok) {
|
|
27682
|
+
const serviceCode = await refusalCode(response);
|
|
27647
27683
|
throw clientError(
|
|
27648
27684
|
"turn_failed",
|
|
27649
27685
|
`Assistant turn failed (${response.status})`,
|
|
27650
27686
|
false,
|
|
27651
|
-
response.status
|
|
27687
|
+
response.status,
|
|
27688
|
+
void 0,
|
|
27689
|
+
serviceCode
|
|
27652
27690
|
);
|
|
27653
27691
|
}
|
|
27654
27692
|
await this.#consume(response);
|
|
@@ -27687,23 +27725,25 @@ var DefaultAssistantClient = class {
|
|
|
27687
27725
|
}
|
|
27688
27726
|
}
|
|
27689
27727
|
async #createSession(signal) {
|
|
27690
|
-
const
|
|
27691
|
-
|
|
27692
|
-
|
|
27693
|
-
|
|
27694
|
-
|
|
27695
|
-
|
|
27696
|
-
|
|
27697
|
-
|
|
27698
|
-
|
|
27699
|
-
|
|
27700
|
-
);
|
|
27728
|
+
const source = this.#source;
|
|
27729
|
+
const response = await this.#requestSession(source, {
|
|
27730
|
+
method: "POST",
|
|
27731
|
+
headers: { Accept: "application/json", "Content-Type": "application/json" },
|
|
27732
|
+
body: JSON.stringify(
|
|
27733
|
+
source.kind === "public" ? { embedId: source.embedId } : this.#context ? { context: this.#context } : {}
|
|
27734
|
+
),
|
|
27735
|
+
credentials: source.kind === "public" ? "omit" : "same-origin",
|
|
27736
|
+
signal
|
|
27737
|
+
});
|
|
27701
27738
|
if (!response.ok) {
|
|
27739
|
+
const serviceCode = await refusalCode(response);
|
|
27702
27740
|
throw clientError(
|
|
27703
27741
|
"session_failed",
|
|
27704
27742
|
`Assistant session failed (${response.status})`,
|
|
27705
|
-
|
|
27706
|
-
response.status
|
|
27743
|
+
serviceCode === void 0 || !UNRETRYABLE_SERVICE_CODES.has(serviceCode),
|
|
27744
|
+
response.status,
|
|
27745
|
+
void 0,
|
|
27746
|
+
serviceCode
|
|
27707
27747
|
);
|
|
27708
27748
|
}
|
|
27709
27749
|
let value;
|
|
@@ -27760,6 +27800,34 @@ var DefaultAssistantClient = class {
|
|
|
27760
27800
|
);
|
|
27761
27801
|
}
|
|
27762
27802
|
}
|
|
27803
|
+
/**
|
|
27804
|
+
* The mint, with the one failure a public page hits that an in-app embed cannot.
|
|
27805
|
+
*
|
|
27806
|
+
* A page that loads the embed script and then blocks `connect-src` fails here as a bare network
|
|
27807
|
+
* rejection — no status, no body, nothing to read. "Assistant request failed" sends a developer
|
|
27808
|
+
* hunting through their own code; naming the directive and the origin ends the search. The message
|
|
27809
|
+
* covers a plain outage too, because from inside the browser the two are indistinguishable.
|
|
27810
|
+
*/
|
|
27811
|
+
async #requestSession(source, init) {
|
|
27812
|
+
try {
|
|
27813
|
+
return await this.#fetch(source.url, init);
|
|
27814
|
+
} catch (error51) {
|
|
27815
|
+
if (init.signal?.aborted) throw error51;
|
|
27816
|
+
if (source.kind !== "public") {
|
|
27817
|
+
throw clientError("session_failed", "Assistant request failed", true, void 0, {
|
|
27818
|
+
cause: error51
|
|
27819
|
+
});
|
|
27820
|
+
}
|
|
27821
|
+
throw clientError(
|
|
27822
|
+
"session_failed",
|
|
27823
|
+
`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).`,
|
|
27824
|
+
true,
|
|
27825
|
+
void 0,
|
|
27826
|
+
{ cause: error51 },
|
|
27827
|
+
"blocked_by_page"
|
|
27828
|
+
);
|
|
27829
|
+
}
|
|
27830
|
+
}
|
|
27763
27831
|
async #request(input, init, failureCode) {
|
|
27764
27832
|
try {
|
|
27765
27833
|
return await this.#fetch(input, init);
|
|
@@ -27881,9 +27949,14 @@ async function readStableErrorCode(response) {
|
|
|
27881
27949
|
if (!isRecord4(value) || typeof value.code !== "string") return void 0;
|
|
27882
27950
|
return /^[A-Za-z0-9_.-]{1,64}$/.test(value.code) ? value.code : void 0;
|
|
27883
27951
|
}
|
|
27884
|
-
function clientError(code, message, retryable, status, options) {
|
|
27952
|
+
function clientError(code, message, retryable, status, options, serviceCode) {
|
|
27885
27953
|
return new AssistantClientError(
|
|
27886
|
-
{
|
|
27954
|
+
{
|
|
27955
|
+
code,
|
|
27956
|
+
...status === void 0 ? {} : { status },
|
|
27957
|
+
retryable,
|
|
27958
|
+
...serviceCode === void 0 ? {} : { serviceCode }
|
|
27959
|
+
},
|
|
27887
27960
|
message,
|
|
27888
27961
|
options
|
|
27889
27962
|
);
|
|
@@ -30976,6 +31049,28 @@ function renderMarkdown(text3) {
|
|
|
30976
31049
|
});
|
|
30977
31050
|
}
|
|
30978
31051
|
|
|
31052
|
+
// src/sign-in-card.ts
|
|
31053
|
+
function createSignInCard(options) {
|
|
31054
|
+
const card = document.createElement("div");
|
|
31055
|
+
card.className = "noodle-assistant-card noodle-assistant-sign-in";
|
|
31056
|
+
card.dataset.tool = options.tool;
|
|
31057
|
+
card.setAttribute("role", "status");
|
|
31058
|
+
const heading = document.createElement("p");
|
|
31059
|
+
heading.className = "noodle-assistant-card-heading";
|
|
31060
|
+
heading.textContent = options.labels.heading;
|
|
31061
|
+
card.append(heading);
|
|
31062
|
+
const action = document.createElement("button");
|
|
31063
|
+
action.type = "button";
|
|
31064
|
+
action.className = "noodle-assistant-card-action";
|
|
31065
|
+
action.textContent = options.labels.action;
|
|
31066
|
+
action.addEventListener("click", () => {
|
|
31067
|
+
action.disabled = true;
|
|
31068
|
+
options.onSignIn();
|
|
31069
|
+
});
|
|
31070
|
+
card.append(action);
|
|
31071
|
+
return card;
|
|
31072
|
+
}
|
|
31073
|
+
|
|
30979
31074
|
// src/element-event-controller.ts
|
|
30980
31075
|
var AssistantElementEventController = class {
|
|
30981
31076
|
#host;
|
|
@@ -31070,6 +31165,15 @@ var AssistantElementEventController = class {
|
|
|
31070
31165
|
);
|
|
31071
31166
|
return;
|
|
31072
31167
|
}
|
|
31168
|
+
if (name21 === "auth_requested" && typeof data.continuation === "string") {
|
|
31169
|
+
this.#appendSignInRequest(
|
|
31170
|
+
String(data.id ?? ""),
|
|
31171
|
+
String(data.tool ?? "this"),
|
|
31172
|
+
data.continuation,
|
|
31173
|
+
String(data.expiresAt ?? "")
|
|
31174
|
+
);
|
|
31175
|
+
return;
|
|
31176
|
+
}
|
|
31073
31177
|
if (name21 === "input_requested" && isRecord5(data.requestedSchema)) {
|
|
31074
31178
|
this.#appendInputRequest(
|
|
31075
31179
|
String(data.id ?? ""),
|
|
@@ -31155,6 +31259,40 @@ var AssistantElementEventController = class {
|
|
|
31155
31259
|
})
|
|
31156
31260
|
);
|
|
31157
31261
|
}
|
|
31262
|
+
/**
|
|
31263
|
+
* The host application owns the login. This renders the prompt and raises
|
|
31264
|
+
* `assistant-sign-in-requested`; the page signs the visitor in however it already does, then its
|
|
31265
|
+
* backend spends the continuation. Nothing here talks to an identity provider.
|
|
31266
|
+
*/
|
|
31267
|
+
#appendSignInRequest(id, tool, continuation, expiresAt) {
|
|
31268
|
+
const messages = this.#host.messages();
|
|
31269
|
+
if (!messages) return;
|
|
31270
|
+
this.#proposalCards.get(id)?.remove();
|
|
31271
|
+
const card = createSignInCard({
|
|
31272
|
+
tool,
|
|
31273
|
+
continuation,
|
|
31274
|
+
expiresAt,
|
|
31275
|
+
labels: {
|
|
31276
|
+
heading: "Sign in to continue",
|
|
31277
|
+
action: "Sign in"
|
|
31278
|
+
},
|
|
31279
|
+
onSignIn: () => this.#host.element.dispatchEvent(
|
|
31280
|
+
new CustomEvent("assistant-sign-in-requested", {
|
|
31281
|
+
detail: { id, tool, continuation, expiresAt },
|
|
31282
|
+
bubbles: true,
|
|
31283
|
+
composed: true
|
|
31284
|
+
})
|
|
31285
|
+
)
|
|
31286
|
+
});
|
|
31287
|
+
messages.append(card);
|
|
31288
|
+
this.#proposalCards.set(id, card);
|
|
31289
|
+
this.#host.revealLatest();
|
|
31290
|
+
this.#host.element.dispatchEvent(
|
|
31291
|
+
new CustomEvent("assistant-sign-in-required", {
|
|
31292
|
+
detail: { id, tool, continuation, expiresAt }
|
|
31293
|
+
})
|
|
31294
|
+
);
|
|
31295
|
+
}
|
|
31158
31296
|
#appendInputRequest(id, message, requestedSchema) {
|
|
31159
31297
|
const messages = this.#host.messages();
|
|
31160
31298
|
if (!messages || !id) return;
|
|
@@ -31974,6 +32112,10 @@ var NoodleAssistantElement = class extends HTMLElementBase {
|
|
|
31974
32112
|
#appearanceStyleSnapshot = /* @__PURE__ */ new Map();
|
|
31975
32113
|
#scroll = new AssistantElementScrollController();
|
|
31976
32114
|
sessionEndpoint = "";
|
|
32115
|
+
/** Public mount: the non-secret embed id `noodle deploy` printed. Mutually exclusive with the above. */
|
|
32116
|
+
embedId = "";
|
|
32117
|
+
/** Only for a dev or self-hosted service; the published snippet needs no origin. */
|
|
32118
|
+
serviceUrl = "";
|
|
31977
32119
|
get appearance() {
|
|
31978
32120
|
return this.#hostAppearance;
|
|
31979
32121
|
}
|
|
@@ -32028,6 +32170,8 @@ var NoodleAssistantElement = class extends HTMLElementBase {
|
|
|
32028
32170
|
connectedCallback() {
|
|
32029
32171
|
if (!this.shadowRoot) this.attachShadow({ mode: "open" });
|
|
32030
32172
|
if (!this.sessionEndpoint) this.sessionEndpoint = this.getAttribute("session-endpoint") ?? "";
|
|
32173
|
+
if (!this.embedId) this.embedId = this.getAttribute("embed-id") ?? "";
|
|
32174
|
+
if (!this.serviceUrl) this.serviceUrl = this.getAttribute("service-url") ?? "";
|
|
32031
32175
|
this.#media = globalThis.matchMedia?.("(prefers-color-scheme: dark)");
|
|
32032
32176
|
this.#media?.addEventListener("change", this.#handleSystemTheme);
|
|
32033
32177
|
document.addEventListener("keydown", this.#handleDocumentKeydown);
|
|
@@ -32199,26 +32343,31 @@ var NoodleAssistantElement = class extends HTMLElementBase {
|
|
|
32199
32343
|
}
|
|
32200
32344
|
};
|
|
32201
32345
|
#ensureClient() {
|
|
32202
|
-
|
|
32346
|
+
const key = sessionSourceKey(this);
|
|
32347
|
+
if (this.#client && this.#clientEndpoint === key) return this.#client;
|
|
32203
32348
|
this.#client?.abort();
|
|
32204
32349
|
this.#unsubscribeClient?.();
|
|
32205
32350
|
this.#sessionBootstrap = void 0;
|
|
32206
|
-
const
|
|
32207
|
-
sessionEndpoint: this.sessionEndpoint,
|
|
32351
|
+
const behavior = {
|
|
32208
32352
|
...this.#context ? { context: this.#context } : {},
|
|
32209
32353
|
clientContext: browserClientContext,
|
|
32210
32354
|
...this.#pageContext ? { pageContext: this.#pageContext } : {},
|
|
32211
32355
|
...this.#modelContext ? { modelContext: this.#modelContext } : {}
|
|
32212
|
-
}
|
|
32356
|
+
};
|
|
32357
|
+
const client = this.embedId ? createAssistantClient({
|
|
32358
|
+
embedId: this.embedId,
|
|
32359
|
+
...this.serviceUrl ? { serviceUrl: this.serviceUrl } : {},
|
|
32360
|
+
...behavior
|
|
32361
|
+
}) : createAssistantClient({ sessionEndpoint: this.sessionEndpoint, ...behavior });
|
|
32213
32362
|
this.#client = client;
|
|
32214
|
-
this.#clientEndpoint =
|
|
32363
|
+
this.#clientEndpoint = key;
|
|
32215
32364
|
this.#unsubscribeClient = client.subscribe((event) => {
|
|
32216
32365
|
if (this.#client === client) this.#handleClientEvent(event);
|
|
32217
32366
|
});
|
|
32218
32367
|
return client;
|
|
32219
32368
|
}
|
|
32220
32369
|
#primeSession() {
|
|
32221
|
-
if (!this.sessionEndpoint) return;
|
|
32370
|
+
if (!this.sessionEndpoint && !this.embedId) return;
|
|
32222
32371
|
const client = this.#ensureClient();
|
|
32223
32372
|
const generation = this.#conversationGeneration;
|
|
32224
32373
|
this.#setSessionState("loading");
|
|
@@ -32342,23 +32491,30 @@ var NoodleAssistantElement = class extends HTMLElementBase {
|
|
|
32342
32491
|
}
|
|
32343
32492
|
const detail = error51 instanceof AssistantClientError ? error51.detail : { code: fallbackCode, retryable: false };
|
|
32344
32493
|
this.#appendRecoveryError(
|
|
32345
|
-
detail.code === "session_expired" ? this.#appearance.labels.sessionExpired : this.#appearance.labels.unavailable
|
|
32494
|
+
detail.code === "session_expired" ? this.#appearance.labels.sessionExpired : this.#appearance.labels.unavailable,
|
|
32495
|
+
// A spent or switched-off daily budget offers no retry. Reconnecting cannot succeed, and every
|
|
32496
|
+
// open tab trying is exactly the load the cap was set to refuse — so the button goes, not just
|
|
32497
|
+
// the alarming words. A spent *session* keeps it: reconnect mints a fresh one, which is the fix.
|
|
32498
|
+
detail.retryable !== false || detail.serviceCode === void 0
|
|
32346
32499
|
);
|
|
32347
32500
|
this.#dispatchError(detail);
|
|
32348
32501
|
}
|
|
32349
|
-
#appendRecoveryError(message) {
|
|
32502
|
+
#appendRecoveryError(message, offerRetry = true) {
|
|
32350
32503
|
if (!this.#messages) return;
|
|
32351
32504
|
this.#messages.querySelector(".conversation-error")?.remove();
|
|
32352
32505
|
const error51 = document.createElement("section");
|
|
32353
32506
|
error51.className = "conversation-error";
|
|
32354
|
-
error51.setAttribute("role", "alert");
|
|
32507
|
+
error51.setAttribute("role", offerRetry ? "alert" : "status");
|
|
32355
32508
|
const text3 = document.createElement("p");
|
|
32356
32509
|
text3.textContent = message;
|
|
32357
|
-
|
|
32358
|
-
|
|
32359
|
-
|
|
32360
|
-
|
|
32361
|
-
|
|
32510
|
+
error51.append(text3);
|
|
32511
|
+
if (offerRetry) {
|
|
32512
|
+
const retry = document.createElement("button");
|
|
32513
|
+
retry.type = "button";
|
|
32514
|
+
retry.textContent = this.#appearance.labels.reconnect;
|
|
32515
|
+
retry.addEventListener("click", () => this.reconnect());
|
|
32516
|
+
error51.append(retry);
|
|
32517
|
+
}
|
|
32362
32518
|
this.#messages.append(error51);
|
|
32363
32519
|
this.#revealLatest();
|
|
32364
32520
|
}
|
|
@@ -32636,7 +32792,9 @@ var NoodleAssistantWithRef = (0, import_react2.forwardRef)(
|
|
|
32636
32792
|
element.addEventListener("assistant-event", assistantEvent);
|
|
32637
32793
|
element.addEventListener("assistant-appearance-warning", appearanceWarning);
|
|
32638
32794
|
registerNoodleAssistant();
|
|
32639
|
-
element.sessionEndpoint = props.sessionEndpoint;
|
|
32795
|
+
element.sessionEndpoint = props.sessionEndpoint ?? "";
|
|
32796
|
+
element.embedId = props.embedId ?? "";
|
|
32797
|
+
element.serviceUrl = props.serviceUrl ?? "";
|
|
32640
32798
|
element.appearance = props.appearance;
|
|
32641
32799
|
element.toggleAttribute("open", props.open === true);
|
|
32642
32800
|
if (props.context) element.updateContext(props.context);
|
|
@@ -32661,12 +32819,16 @@ var NoodleAssistantWithRef = (0, import_react2.forwardRef)(
|
|
|
32661
32819
|
props.onSessionExpired,
|
|
32662
32820
|
props.open,
|
|
32663
32821
|
props.pageContext,
|
|
32664
|
-
props.sessionEndpoint
|
|
32822
|
+
props.sessionEndpoint,
|
|
32823
|
+
props.embedId,
|
|
32824
|
+
props.serviceUrl
|
|
32665
32825
|
]);
|
|
32666
32826
|
return (0, import_react2.createElement)("noodle-assistant", {
|
|
32667
32827
|
ref,
|
|
32668
32828
|
class: props.className,
|
|
32669
|
-
"session-endpoint": props.sessionEndpoint,
|
|
32829
|
+
...props.sessionEndpoint ? { "session-endpoint": props.sessionEndpoint } : {},
|
|
32830
|
+
...props.embedId ? { "embed-id": props.embedId } : {},
|
|
32831
|
+
...props.serviceUrl ? { "service-url": props.serviceUrl } : {},
|
|
32670
32832
|
...props.theme ? { theme: props.theme } : {}
|
|
32671
32833
|
});
|
|
32672
32834
|
}
|