| 208 | * {@link DestroyRef}. |
| 209 | */ |
| 210 | export class ThreadsStore implements InjectThreadsResult { |
| 211 | readonly #copilotkit = inject(CopilotKit); |
| 212 | readonly #store: ɵThreadStore = ɵcreateThreadStore({ |
| 213 | // Cast to `typeof fetch`: the wrapper preserves correct `this` binding for |
| 214 | // globalThis.fetch but does not re-expose static members (e.g. `preconnect`) |
| 215 | // that newer DOM libs add and that the store never calls. |
| 216 | fetch: ((...args: Parameters<typeof fetch>) => |
| 217 | globalThis.fetch(...args)) as typeof fetch, |
| 218 | }); |
| 219 | readonly #subscriptions: Subscription[] = []; |
| 220 | |
| 221 | readonly #threads = signal<Thread[]>([]); |
| 222 | readonly #storeIsLoading = signal<boolean>(false); |
| 223 | readonly #storeError = signal<Error | null>(null); |
| 224 | readonly #hasMoreThreads = signal<boolean>(false); |
| 225 | readonly #isFetchingMoreThreads = signal<boolean>(false); |
| 226 | readonly #isMutating = signal<boolean>(false); |
| 227 | |
| 228 | /** |
| 229 | * Tracks whether a real runtime context has been dispatched to the store |
| 230 | * yet. The store itself starts `isLoading: false`, so before the first |
| 231 | * dispatch consumers would otherwise see an empty, non-loading list (the |
| 232 | * empty-list flash). While a runtime URL is configured and the endpoints |
| 233 | * are available but no context has been dispatched, we synthesize loading. |
| 234 | */ |
| 235 | readonly #hasDispatchedContext = signal<boolean>(false); |
| 236 | |
| 237 | readonly threads = this.#threads.asReadonly(); |
| 238 | readonly error: Signal<Error | null>; |
| 239 | readonly listError: Signal<Error | null>; |
| 240 | readonly isLoading: Signal<boolean>; |
| 241 | readonly hasMoreThreads = this.#hasMoreThreads.asReadonly(); |
| 242 | readonly isFetchingMoreThreads = this.#isFetchingMoreThreads.asReadonly(); |
| 243 | readonly isMutating = this.#isMutating.asReadonly(); |
| 244 | |
| 245 | constructor(input: InjectThreadsInput, destroyRef: DestroyRef) { |
| 246 | const agentId = toAccessor(input.agentId); |
| 247 | const includeArchived = toAccessor(input.includeArchived); |
| 248 | const limit = toAccessor(input.limit); |
| 249 | const enabled = toAccessor(input.enabled); |
| 250 | // `enabled` defaults to `true`; only an explicit `false` keeps the store |
| 251 | // inert. An unset/`undefined` input is treated as enabled. |
| 252 | const isEnabled = (): boolean => enabled() !== false; |
| 253 | |
| 254 | this.#bridgeSelectors(); |
| 255 | this.#store.start(); |
| 256 | |
| 257 | // Synthesized error/loading reconcile the core store's internal state with |
| 258 | // configuration-level conditions (no runtime URL, unavailable endpoints), |
| 259 | // mirroring react-core's useThreads. |
| 260 | const runtimeUrl = this.#copilotkit.runtimeUrl; |
| 261 | const runtimeStatus = this.#copilotkit.runtimeConnectionStatus; |
| 262 | // Read `threadEndpoints`/`intelligence.wsUrl` through the CopilotKit |
| 263 | // signals (not plain `core.*` getters) so the computeds/effect re-run when |
| 264 | // `/info` populates them — even if it lands without a connection-status |
| 265 | // transition. This mirrors react-core, which lists both in its effect deps. |
| 266 | const threadEndpoints = this.#copilotkit.threadEndpoints; |
| 267 | const threadListSupported = (): boolean => |
nothing calls this directly
no test coverage detected
searching dependent graphs…