@palettelab/sdk 0.1.14 → 0.1.16

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/index.mjs CHANGED
@@ -171,6 +171,7 @@ function createMockPlatformContext(overrides = {}) {
171
171
  apiFetch: async () => new Response(JSON.stringify({}), { status: 200 }),
172
172
  navigate: () => {
173
173
  },
174
+ routePath: "/",
174
175
  showToast: () => {
175
176
  },
176
177
  ...overrides
@@ -302,6 +303,8 @@ function hasAllPermissions(ctx, permissions) {
302
303
  }
303
304
 
304
305
  // src/storage.ts
306
+ var DEFAULT_CHUNK_SIZE = 8 * 1024 * 1024;
307
+ var MIN_CHUNK_SIZE = 256 * 1024;
305
308
  async function uploadToSignedUrl(uploadUrl, file, contentType = "application/octet-stream") {
306
309
  const res = await fetch(uploadUrl, {
307
310
  method: "PUT",
@@ -310,10 +313,178 @@ async function uploadToSignedUrl(uploadUrl, file, contentType = "application/oct
310
313
  });
311
314
  if (!res.ok) throw new Error(`Upload failed: ${res.statusText}`);
312
315
  }
316
+ function normalizeChunkSize(value) {
317
+ const raw = Math.max(value || DEFAULT_CHUNK_SIZE, MIN_CHUNK_SIZE);
318
+ return Math.ceil(raw / MIN_CHUNK_SIZE) * MIN_CHUNK_SIZE;
319
+ }
320
+ function defaultPluginId() {
321
+ if (typeof window === "undefined") return "";
322
+ const match = window.location.pathname.match(/\/apps\/([^/?#]+)/);
323
+ return match ? decodeURIComponent(match[1]) : "";
324
+ }
325
+ function fileFingerprint(pluginId, file, key) {
326
+ return [pluginId, key || file.name, file.size, file.lastModified].join(":");
327
+ }
328
+ function sessionStorageKey(pluginId, fingerprint) {
329
+ return `palette:storage-upload:${pluginId}:${fingerprint}`;
330
+ }
331
+ function readStoredSession(pluginId, fingerprint) {
332
+ if (typeof window === "undefined") return null;
333
+ try {
334
+ const raw = window.localStorage.getItem(sessionStorageKey(pluginId, fingerprint));
335
+ if (!raw) return null;
336
+ const session = JSON.parse(raw);
337
+ return session.plugin_id === pluginId && session.fingerprint === fingerprint ? session : null;
338
+ } catch {
339
+ return null;
340
+ }
341
+ }
342
+ function writeStoredSession(pluginId, fingerprint, session) {
343
+ if (typeof window === "undefined") return;
344
+ const stored = { ...session, plugin_id: pluginId, fingerprint, updated_at: Date.now() };
345
+ window.localStorage.setItem(sessionStorageKey(pluginId, fingerprint), JSON.stringify(stored));
346
+ }
347
+ function clearStoredSession(pluginId, fingerprint) {
348
+ if (typeof window === "undefined") return;
349
+ window.localStorage.removeItem(sessionStorageKey(pluginId, fingerprint));
350
+ }
351
+ function absoluteApiUrl(pathOrUrl) {
352
+ return /^https?:\/\//i.test(pathOrUrl) ? pathOrUrl : `${getBaseUrl()}${pathOrUrl}`;
353
+ }
354
+ function isPaletteApiUpload(url) {
355
+ return url.startsWith(getBaseUrl()) || url.startsWith("/");
356
+ }
357
+ function report(loaded, total, chunkIndex, chunkCount, state, onProgress) {
358
+ onProgress?.({
359
+ loaded,
360
+ total,
361
+ percentage: total > 0 ? Math.min(100, Math.round(loaded / total * 1e4) / 100) : 100,
362
+ chunkIndex,
363
+ chunkCount,
364
+ state
365
+ });
366
+ }
367
+ function xhrPut(url, body, headers, signal, onUploadProgress) {
368
+ return new Promise((resolve, reject) => {
369
+ const xhr = new XMLHttpRequest();
370
+ xhr.open("PUT", absoluteApiUrl(url), true);
371
+ xhr.withCredentials = isPaletteApiUpload(url);
372
+ for (const [key, value] of Object.entries(headers)) xhr.setRequestHeader(key, value);
373
+ xhr.upload.onprogress = (event) => {
374
+ if (event.lengthComputable) onUploadProgress?.(event.loaded);
375
+ };
376
+ xhr.onload = () => {
377
+ const ok = [200, 201, 204, 308].includes(xhr.status);
378
+ if (!ok) reject(new Error(`Upload failed: ${xhr.status} ${xhr.statusText}`));
379
+ else resolve({ status: xhr.status, range: xhr.getResponseHeader("Range") });
380
+ };
381
+ xhr.onerror = () => reject(new Error("Upload failed"));
382
+ xhr.onabort = () => reject(new DOMException("Upload aborted", "AbortError"));
383
+ if (signal) {
384
+ if (signal.aborted) {
385
+ xhr.abort();
386
+ return;
387
+ }
388
+ signal.addEventListener("abort", () => xhr.abort(), { once: true });
389
+ }
390
+ xhr.send(body);
391
+ });
392
+ }
393
+ function uploadedFromRange(range) {
394
+ if (!range) return 0;
395
+ const match = range.match(/bytes=0-(\d+)/);
396
+ return match ? Number(match[1]) + 1 : 0;
397
+ }
398
+ async function queryGcsOffset(session) {
399
+ try {
400
+ const result = await xhrPut(session.upload_url, new Blob([]), {
401
+ "Content-Range": `bytes */${session.size}`
402
+ });
403
+ if ([200, 201, 204].includes(result.status)) return session.size;
404
+ return uploadedFromRange(result.range);
405
+ } catch {
406
+ return 0;
407
+ }
408
+ }
409
+ async function queryLocalOffset(session) {
410
+ const res = await fetch(absoluteApiUrl(session.status_url), { credentials: "include" });
411
+ if (!res.ok) return 0;
412
+ const body = await res.json();
413
+ return Number(body.uploaded_bytes || 0);
414
+ }
415
+ async function queryOffset(session) {
416
+ if (session.mode === "local_resumable") return queryLocalOffset(session);
417
+ return queryGcsOffset(session);
418
+ }
313
419
  var StorageClient = class {
314
- constructor() {
420
+ constructor(ctx) {
421
+ this.ctx = ctx;
315
422
  this.uploadToSignedUrl = uploadToSignedUrl;
316
423
  }
424
+ pluginId(explicit) {
425
+ const resolved = explicit || this.ctx?.pluginId || defaultPluginId();
426
+ if (!resolved) throw new Error("pluginId is required for app storage uploads");
427
+ return resolved;
428
+ }
429
+ async createSession(pluginId, file, options) {
430
+ const res = await (this.ctx?.apiFetch || apiFetch)(`/api/v1/app-storage/${encodeURIComponent(pluginId)}/uploads`, {
431
+ method: "POST",
432
+ body: JSON.stringify({
433
+ filename: file.name,
434
+ content_type: options.contentType || file.type || "application/octet-stream",
435
+ size: file.size,
436
+ key: options.key || null,
437
+ resumable: options.resumable ?? true,
438
+ chunk_size: normalizeChunkSize(options.chunkSize)
439
+ })
440
+ });
441
+ return res.json();
442
+ }
443
+ async upload(file, options = {}) {
444
+ const pluginId = this.pluginId(options.pluginId);
445
+ const fingerprint = fileFingerprint(pluginId, file, options.key);
446
+ const stored = options.resumable === false ? null : readStoredSession(pluginId, fingerprint);
447
+ const session = stored || await this.createSession(pluginId, file, options);
448
+ writeStoredSession(pluginId, fingerprint, session);
449
+ const chunkSize = normalizeChunkSize(options.chunkSize || session.chunk_size);
450
+ const chunkCount = Math.max(1, Math.ceil(file.size / chunkSize));
451
+ let offset = stored ? await queryOffset(session) : 0;
452
+ report(offset, file.size, Math.floor(offset / chunkSize), chunkCount, "starting", options.onProgress);
453
+ while (offset < file.size) {
454
+ if (options.signal?.aborted) throw new DOMException("Upload aborted", "AbortError");
455
+ const start = offset;
456
+ const end = Math.min(file.size, start + chunkSize) - 1;
457
+ const chunk = file.slice(start, end + 1);
458
+ const chunkIndex = Math.floor(start / chunkSize) + 1;
459
+ const lastReportedBase = start;
460
+ await xhrPut(
461
+ session.upload_url,
462
+ chunk,
463
+ {
464
+ "Content-Type": session.content_type,
465
+ "Content-Range": `bytes ${start}-${end}/${file.size}`
466
+ },
467
+ options.signal,
468
+ (loaded) => report(lastReportedBase + loaded, file.size, chunkIndex, chunkCount, "uploading", options.onProgress)
469
+ );
470
+ offset = end + 1;
471
+ report(offset, file.size, chunkIndex, chunkCount, offset >= file.size ? "complete" : "uploading", options.onProgress);
472
+ writeStoredSession(pluginId, fingerprint, session);
473
+ }
474
+ clearStoredSession(pluginId, fingerprint);
475
+ return {
476
+ uploadId: session.upload_id,
477
+ mode: session.mode,
478
+ bucket: session.bucket,
479
+ objectPath: session.object_path,
480
+ fileUrl: session.file_url,
481
+ contentType: session.content_type,
482
+ size: session.size
483
+ };
484
+ }
485
+ async resume(file, options = {}) {
486
+ return this.upload(file, { ...options, resumable: true });
487
+ }
317
488
  };
318
489
 
319
490
  // src/user-org.ts
@@ -391,7 +562,7 @@ var OrganizationClient = class {
391
562
 
392
563
  // src/palette-client.ts
393
564
  function createPaletteClient(ctx) {
394
- const defaultPluginId = () => {
565
+ const defaultPluginId2 = () => {
395
566
  if (ctx?.pluginId) return ctx.pluginId;
396
567
  if (typeof window === "undefined") return "";
397
568
  const match = window.location.pathname.match(/\/apps\/([^/?#]+)/);
@@ -401,15 +572,15 @@ function createPaletteClient(ctx) {
401
572
  user: new UserClient(ctx),
402
573
  organization: new OrganizationClient(ctx),
403
574
  dataRooms: new DataRoomClient(),
404
- storage: new StorageClient(),
575
+ storage: new StorageClient(ctx),
405
576
  config: {
406
577
  get: (pluginId = ctx?.pluginId ?? "") => {
407
- const resolved = pluginId || defaultPluginId();
578
+ const resolved = pluginId || defaultPluginId2();
408
579
  if (!resolved) throw new Error("pluginId is required to read install config");
409
580
  return getInstallConfig(resolved);
410
581
  },
411
582
  update: (values, pluginId = ctx?.pluginId ?? "") => {
412
- const resolved = pluginId || defaultPluginId();
583
+ const resolved = pluginId || defaultPluginId2();
413
584
  if (!resolved) throw new Error("pluginId is required to update install config");
414
585
  return updateInstallConfig(resolved, values);
415
586
  }
@@ -490,14 +661,216 @@ function usePluginTranslations(resources, options = {}) {
490
661
  };
491
662
  }
492
663
 
664
+ // src/router.tsx
665
+ import {
666
+ Component,
667
+ createContext as createContext2,
668
+ createElement as createElement2,
669
+ useCallback as useCallback2,
670
+ useContext as useContext2,
671
+ useEffect,
672
+ useMemo,
673
+ useState
674
+ } from "react";
675
+ var RouterCtx = createContext2(null);
676
+ var NOT_FOUND = /* @__PURE__ */ Symbol.for("palette.router.not-found");
677
+ function notFound() {
678
+ throw Object.assign(new Error("Palette route not found"), { code: NOT_FOUND });
679
+ }
680
+ function normalizePath(path) {
681
+ const clean = String(path || "/").split("#")[0].split("?")[0] || "/";
682
+ return `/${clean.replace(/^\/+/, "").replace(/\/+$/, "")}`.replace(/^\/$/, "/");
683
+ }
684
+ function splitPath(path) {
685
+ return normalizePath(path).split("/").filter(Boolean).map(decodeURIComponent);
686
+ }
687
+ function routeScore(route) {
688
+ if (typeof route.score === "number") return route.score;
689
+ return route.segments.reduce((score, segment) => {
690
+ if (segment.kind === "static") return score + 10;
691
+ if (segment.kind === "dynamic") return score + 5;
692
+ return score + (segment.optional ? 1 : 2);
693
+ }, route.segments.length);
694
+ }
695
+ function matchRoute(route, path) {
696
+ const parts = splitPath(path);
697
+ const params = {};
698
+ let index = 0;
699
+ for (const segment of route.segments) {
700
+ if (segment.kind === "catchAll") {
701
+ const rest = parts.slice(index);
702
+ if (!segment.optional && rest.length === 0) return null;
703
+ params[segment.name] = rest;
704
+ index = parts.length;
705
+ break;
706
+ }
707
+ const value = parts[index];
708
+ if (value === void 0) return null;
709
+ if (segment.kind === "static") {
710
+ if (value !== segment.value) return null;
711
+ } else {
712
+ params[segment.name] = value;
713
+ }
714
+ index += 1;
715
+ }
716
+ return index === parts.length ? params : null;
717
+ }
718
+ function currentPluginPath(pluginId, routePath) {
719
+ if (routePath) return normalizePath(routePath);
720
+ if (typeof window === "undefined") return "/";
721
+ const path = window.location.pathname;
722
+ if (pluginId) {
723
+ const prefix = `/apps/${pluginId}`;
724
+ if (path === prefix) return "/";
725
+ if (path.startsWith(`${prefix}/`)) return normalizePath(path.slice(prefix.length));
726
+ }
727
+ return normalizePath(path);
728
+ }
729
+ function currentSearchParams() {
730
+ if (typeof window === "undefined") return new URLSearchParams();
731
+ return new URLSearchParams(window.location.search);
732
+ }
733
+ var PaletteRouteErrorBoundary = class extends Component {
734
+ constructor() {
735
+ super(...arguments);
736
+ this.state = { error: null };
737
+ this.reset = () => this.setState({ error: null });
738
+ }
739
+ static getDerivedStateFromError(error) {
740
+ return { error };
741
+ }
742
+ componentDidCatch(_error, _info) {
743
+ }
744
+ render() {
745
+ const error = this.state.error;
746
+ if (!error) return this.props.children;
747
+ if (error.code === NOT_FOUND && this.props.notFound) {
748
+ return createElement2(this.props.notFound);
749
+ }
750
+ if (this.props.fallback) {
751
+ return createElement2(this.props.fallback, { error, reset: this.reset });
752
+ }
753
+ throw error;
754
+ }
755
+ };
756
+ function renderRoute(route) {
757
+ const page = createElement2(route.page);
758
+ const wrapped = (route.layouts || []).reduceRight(
759
+ (children, Layout) => createElement2(Layout, null, children),
760
+ page
761
+ );
762
+ return createElement2(
763
+ PaletteRouteErrorBoundary,
764
+ { fallback: route.error, notFound: route.notFound },
765
+ wrapped
766
+ );
767
+ }
768
+ function PaletteAppRouter({
769
+ routes,
770
+ notFound: NotFound
771
+ }) {
772
+ const platform = usePlatform();
773
+ const [location, setLocation] = useState(() => ({
774
+ pathname: currentPluginPath(platform.pluginId, platform.routePath),
775
+ searchParams: currentSearchParams()
776
+ }));
777
+ useEffect(() => {
778
+ setLocation({
779
+ pathname: currentPluginPath(platform.pluginId, platform.routePath),
780
+ searchParams: currentSearchParams()
781
+ });
782
+ }, [platform.pluginId, platform.routePath]);
783
+ useEffect(() => {
784
+ const sync = () => setLocation({
785
+ pathname: currentPluginPath(platform.pluginId, platform.routePath),
786
+ searchParams: currentSearchParams()
787
+ });
788
+ window.addEventListener("popstate", sync);
789
+ return () => window.removeEventListener("popstate", sync);
790
+ }, [platform.pluginId, platform.routePath]);
791
+ const sortedRoutes = useMemo(
792
+ () => [...routes].sort((a, b) => routeScore(b) - routeScore(a)),
793
+ [routes]
794
+ );
795
+ const matched = useMemo(() => {
796
+ for (const route of sortedRoutes) {
797
+ const params = matchRoute(route, location.pathname);
798
+ if (params) return { route, params };
799
+ }
800
+ return null;
801
+ }, [location.pathname, sortedRoutes]);
802
+ const navigate = useCallback2((target, replace = false) => {
803
+ const [pathPart, queryPart = ""] = String(target || "/").split("?");
804
+ const pathname = normalizePath(pathPart);
805
+ const next = `${pathname}${queryPart ? `?${queryPart}` : ""}`;
806
+ setLocation({ pathname, searchParams: new URLSearchParams(queryPart) });
807
+ const currentPath = typeof window === "undefined" ? "" : window.location.pathname;
808
+ const inPalettePath = platform.pluginId && (currentPath.startsWith(`/apps/${platform.pluginId}`) || platform.routePath !== void 0);
809
+ const osPath = inPalettePath && platform.pluginId ? `/apps/${platform.pluginId}${pathname === "/" ? "" : pathname}${queryPart ? `?${queryPart}` : ""}` : next;
810
+ if (replace && typeof window !== "undefined") window.history.replaceState(null, "", osPath);
811
+ else platform.navigate(osPath);
812
+ }, [platform]);
813
+ const state = useMemo(() => ({
814
+ pathname: location.pathname,
815
+ searchParams: location.searchParams,
816
+ params: matched?.params || {},
817
+ push: (path) => navigate(path, false),
818
+ replace: (path) => navigate(path, true)
819
+ }), [location.pathname, location.searchParams, matched?.params, navigate]);
820
+ const content = matched ? renderRoute(matched.route) : NotFound ? createElement2(NotFound) : createElement2("div", null, "Page not found");
821
+ return createElement2(RouterCtx.Provider, { value: state }, content);
822
+ }
823
+ function useRouterState() {
824
+ const value = useContext2(RouterCtx);
825
+ if (!value) throw new Error("Palette app router hooks must be used inside PaletteAppRouter");
826
+ return value;
827
+ }
828
+ function useRouter() {
829
+ const { push, replace } = useRouterState();
830
+ return { push, replace, back: () => window.history.back(), forward: () => window.history.forward() };
831
+ }
832
+ function usePathname() {
833
+ return useRouterState().pathname;
834
+ }
835
+ function useSearchParams() {
836
+ return useRouterState().searchParams;
837
+ }
838
+ function useParams() {
839
+ return useRouterState().params;
840
+ }
841
+ function Link({
842
+ href,
843
+ replace,
844
+ onClick,
845
+ children,
846
+ ...props
847
+ }) {
848
+ const router = useRouter();
849
+ return createElement2(
850
+ "a",
851
+ {
852
+ ...props,
853
+ href,
854
+ onClick: (event) => {
855
+ onClick?.(event);
856
+ if (event.defaultPrevented || event.metaKey || event.ctrlKey || event.shiftKey || event.altKey) return;
857
+ event.preventDefault();
858
+ if (replace) router.replace(href);
859
+ else router.push(href);
860
+ }
861
+ },
862
+ children
863
+ );
864
+ }
865
+
493
866
  // src/hooks/use-plugin-tasks.ts
494
- import { useCallback as useCallback2, useEffect, useState } from "react";
867
+ import { useCallback as useCallback3, useEffect as useEffect2, useState as useState2 } from "react";
495
868
  function usePluginTasks(agentId) {
496
869
  const { apiFetch: apiFetch2 } = usePlatform();
497
- const [tasks, setTasks] = useState([]);
498
- const [stats, setStats] = useState(null);
499
- const [loading, setLoading] = useState(true);
500
- const fetchTasks = useCallback2(async () => {
870
+ const [tasks, setTasks] = useState2([]);
871
+ const [stats, setStats] = useState2(null);
872
+ const [loading, setLoading] = useState2(true);
873
+ const fetchTasks = useCallback3(async () => {
501
874
  try {
502
875
  const params = new URLSearchParams();
503
876
  if (agentId) params.set("agent_id", String(agentId));
@@ -506,18 +879,18 @@ function usePluginTasks(agentId) {
506
879
  } catch {
507
880
  }
508
881
  }, [apiFetch2, agentId]);
509
- const fetchStats = useCallback2(async () => {
882
+ const fetchStats = useCallback3(async () => {
510
883
  try {
511
884
  const res = await apiFetch2("/api/v1/tasks/stats");
512
885
  setStats(await res.json());
513
886
  } catch {
514
887
  }
515
888
  }, [apiFetch2]);
516
- useEffect(() => {
889
+ useEffect2(() => {
517
890
  setLoading(true);
518
891
  Promise.all([fetchTasks(), fetchStats()]).finally(() => setLoading(false));
519
892
  }, [fetchTasks, fetchStats]);
520
- const createTask = useCallback2(async (payload) => {
893
+ const createTask = useCallback3(async (payload) => {
521
894
  const res = await apiFetch2("/api/v1/tasks", {
522
895
  method: "POST",
523
896
  body: JSON.stringify(payload)
@@ -526,7 +899,7 @@ function usePluginTasks(agentId) {
526
899
  await Promise.all([fetchTasks(), fetchStats()]);
527
900
  return task;
528
901
  }, [apiFetch2, fetchTasks, fetchStats]);
529
- const updateTask = useCallback2(async (taskId, payload) => {
902
+ const updateTask = useCallback3(async (taskId, payload) => {
530
903
  const res = await apiFetch2(`/api/v1/tasks/${taskId}`, {
531
904
  method: "PATCH",
532
905
  body: JSON.stringify(payload)
@@ -535,7 +908,7 @@ function usePluginTasks(agentId) {
535
908
  await Promise.all([fetchTasks(), fetchStats()]);
536
909
  return task;
537
910
  }, [apiFetch2, fetchTasks, fetchStats]);
538
- const deleteTask = useCallback2(async (taskId) => {
911
+ const deleteTask = useCallback3(async (taskId) => {
539
912
  await apiFetch2(`/api/v1/tasks/${taskId}`, { method: "DELETE" });
540
913
  await Promise.all([fetchTasks(), fetchStats()]);
541
914
  }, [apiFetch2, fetchTasks, fetchStats]);
@@ -543,23 +916,23 @@ function usePluginTasks(agentId) {
543
916
  }
544
917
 
545
918
  // src/hooks/use-plugin-data-rooms.ts
546
- import { useCallback as useCallback3, useEffect as useEffect2, useState as useState2 } from "react";
919
+ import { useCallback as useCallback4, useEffect as useEffect3, useState as useState3 } from "react";
547
920
  function usePluginDataRooms() {
548
921
  const { apiFetch: apiFetch2 } = usePlatform();
549
- const [rooms, setRooms] = useState2([]);
550
- const [loading, setLoading] = useState2(true);
551
- const fetchRooms = useCallback3(async () => {
922
+ const [rooms, setRooms] = useState3([]);
923
+ const [loading, setLoading] = useState3(true);
924
+ const fetchRooms = useCallback4(async () => {
552
925
  try {
553
926
  const res = await apiFetch2("/api/v1/data-rooms");
554
927
  setRooms(await res.json());
555
928
  } catch {
556
929
  }
557
930
  }, [apiFetch2]);
558
- useEffect2(() => {
931
+ useEffect3(() => {
559
932
  setLoading(true);
560
933
  fetchRooms().finally(() => setLoading(false));
561
934
  }, [fetchRooms]);
562
- const fetchFolder = useCallback3(async (roomId, folderId) => {
935
+ const fetchFolder = useCallback4(async (roomId, folderId) => {
563
936
  const path = folderId ? `/api/v1/data-rooms/${roomId}/folders/${folderId}` : `/api/v1/data-rooms/${roomId}`;
564
937
  const res = await apiFetch2(path);
565
938
  return res.json();
@@ -568,19 +941,19 @@ function usePluginDataRooms() {
568
941
  }
569
942
 
570
943
  // src/hooks/use-plugin-chat.ts
571
- import { useCallback as useCallback4, useRef, useState as useState3 } from "react";
944
+ import { useCallback as useCallback5, useRef, useState as useState4 } from "react";
572
945
  function usePluginChat(agentId) {
573
946
  const { apiFetch: apiFetch2 } = usePlatform();
574
- const [threads, setThreads] = useState3([]);
575
- const [messages, setMessages] = useState3([]);
576
- const [streaming, setStreaming] = useState3(false);
577
- const [activeThreadId, setActiveThreadId] = useState3(null);
947
+ const [threads, setThreads] = useState4([]);
948
+ const [messages, setMessages] = useState4([]);
949
+ const [streaming, setStreaming] = useState4(false);
950
+ const [activeThreadId, setActiveThreadId] = useState4(null);
578
951
  const abortRef = useRef(null);
579
- const fetchThreads = useCallback4(async () => {
952
+ const fetchThreads = useCallback5(async () => {
580
953
  const res = await apiFetch2(`/api/v1/chat/${agentId}/threads`);
581
954
  setThreads(await res.json());
582
955
  }, [apiFetch2, agentId]);
583
- const createThread = useCallback4(async () => {
956
+ const createThread = useCallback5(async () => {
584
957
  const res = await apiFetch2(`/api/v1/chat/${agentId}/threads`, { method: "POST" });
585
958
  const thread = await res.json();
586
959
  setActiveThreadId(thread.id);
@@ -588,13 +961,13 @@ function usePluginChat(agentId) {
588
961
  await fetchThreads();
589
962
  return thread;
590
963
  }, [apiFetch2, agentId, fetchThreads]);
591
- const fetchMessages = useCallback4(async (threadId) => {
964
+ const fetchMessages = useCallback5(async (threadId) => {
592
965
  const res = await apiFetch2(`/api/v1/chat/threads/${threadId}/messages`);
593
966
  const msgs = await res.json();
594
967
  setMessages(msgs);
595
968
  setActiveThreadId(threadId);
596
969
  }, [apiFetch2]);
597
- const sendMessage = useCallback4(async (threadId, content) => {
970
+ const sendMessage = useCallback5(async (threadId, content) => {
598
971
  setStreaming(true);
599
972
  abortRef.current = new AbortController();
600
973
  const userMsg = {
@@ -662,7 +1035,7 @@ function usePluginChat(agentId) {
662
1035
  abortRef.current = null;
663
1036
  }
664
1037
  }, []);
665
- const stopStreaming = useCallback4(() => {
1038
+ const stopStreaming = useCallback5(() => {
666
1039
  abortRef.current?.abort();
667
1040
  }, []);
668
1041
  return {
@@ -679,8 +1052,10 @@ function usePluginChat(agentId) {
679
1052
  }
680
1053
  export {
681
1054
  DataRoomClient,
1055
+ Link,
682
1056
  OrganizationClient,
683
1057
  PaletteApiError,
1058
+ PaletteAppRouter,
684
1059
  PlatformCtx,
685
1060
  PluginProvider,
686
1061
  StorageClient,
@@ -700,14 +1075,19 @@ export {
700
1075
  isPaletteApiError,
701
1076
  isSandboxRuntime,
702
1077
  normalizePaletteLanguage,
1078
+ notFound,
703
1079
  setBaseUrl,
704
1080
  translate,
705
1081
  updateInstallConfig,
706
1082
  uploadToSignedUrl,
1083
+ useParams,
1084
+ usePathname,
707
1085
  usePlatform,
708
1086
  usePluginChat,
709
1087
  usePluginDataRooms,
710
1088
  usePluginTasks,
711
1089
  usePluginTranslations,
1090
+ useRouter,
1091
+ useSearchParams,
712
1092
  withPluginProvider
713
1093
  };
@@ -106,7 +106,7 @@ interface PluginManifest {
106
106
  frontend?: {
107
107
  entry: string;
108
108
  sandbox?: boolean;
109
- framework?: "react" | "next";
109
+ framework?: "react" | "next" | "palette-app";
110
110
  config?: string;
111
111
  };
112
112
  backend?: {
@@ -165,6 +165,8 @@ interface PlatformContext {
165
165
  apiFetch: (path: string, init?: RequestInit) => Promise<Response>;
166
166
  /** Navigate to a platform route */
167
167
  navigate: (path: string) => void;
168
+ /** Current route path within the plugin app, when mounted by Palette OS */
169
+ routePath?: string;
168
170
  /** Show a toast notification */
169
171
  showToast: (message: string, type?: "success" | "error" | "info") => void;
170
172
  /** Permissions declared for the current plugin install/runtime */
@@ -106,7 +106,7 @@ interface PluginManifest {
106
106
  frontend?: {
107
107
  entry: string;
108
108
  sandbox?: boolean;
109
- framework?: "react" | "next";
109
+ framework?: "react" | "next" | "palette-app";
110
110
  config?: string;
111
111
  };
112
112
  backend?: {
@@ -165,6 +165,8 @@ interface PlatformContext {
165
165
  apiFetch: (path: string, init?: RequestInit) => Promise<Response>;
166
166
  /** Navigate to a platform route */
167
167
  navigate: (path: string) => void;
168
+ /** Current route path within the plugin app, when mounted by Palette OS */
169
+ routePath?: string;
168
170
  /** Show a toast notification */
169
171
  showToast: (message: string, type?: "success" | "error" | "info") => void;
170
172
  /** Permissions declared for the current plugin install/runtime */