theokit 0.26.0 → 0.28.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.
Files changed (29) hide show
  1. package/dist/{agent-GXJNFW6N.js → agent-I5QEOMCN.js} +2 -2
  2. package/dist/{agents-typed-client-RIJVHSX4.js → agents-typed-client-5DCGDWFZ.js} +8 -2
  3. package/dist/agents-typed-client-5DCGDWFZ.js.map +1 -0
  4. package/dist/{agents-typed-client-DCPSXUU5.js → agents-typed-client-MAAILRI6.js} +8 -2
  5. package/dist/agents-typed-client-MAAILRI6.js.map +1 -0
  6. package/dist/{build-WD2PT5TA.js → build-V2QEBE7I.js} +2 -2
  7. package/dist/{chunk-2JNG3P4Y.js → chunk-R4VVXLKZ.js} +2 -2
  8. package/dist/{chunk-GG6H76GC.js → chunk-VRVNM3QL.js} +2 -2
  9. package/dist/{chunk-LVGDYTU2.js → chunk-Y4TOVMNC.js} +2 -2
  10. package/dist/cli/index.js +4 -4
  11. package/dist/client/index.d.ts +224 -19
  12. package/dist/client/index.js +402 -62
  13. package/dist/client/index.js.map +1 -1
  14. package/dist/{dev-63UNEUF6.js → dev-5ZQMWVFC.js} +2 -2
  15. package/dist/index.js +1 -1
  16. package/dist/{mcp-MIZFVNCL.js → mcp-VMKWB2BY.js} +2 -2
  17. package/dist/vite-plugin/index.js +1 -1
  18. package/dist/{vite-plugin-ST4JZQRJ.js → vite-plugin-BNI7YLCD.js} +2 -2
  19. package/package.json +1 -1
  20. package/dist/agents-typed-client-DCPSXUU5.js.map +0 -1
  21. package/dist/agents-typed-client-RIJVHSX4.js.map +0 -1
  22. /package/dist/{agent-GXJNFW6N.js.map → agent-I5QEOMCN.js.map} +0 -0
  23. /package/dist/{build-WD2PT5TA.js.map → build-V2QEBE7I.js.map} +0 -0
  24. /package/dist/{chunk-2JNG3P4Y.js.map → chunk-R4VVXLKZ.js.map} +0 -0
  25. /package/dist/{chunk-GG6H76GC.js.map → chunk-VRVNM3QL.js.map} +0 -0
  26. /package/dist/{chunk-LVGDYTU2.js.map → chunk-Y4TOVMNC.js.map} +0 -0
  27. /package/dist/{dev-63UNEUF6.js.map → dev-5ZQMWVFC.js.map} +0 -0
  28. /package/dist/{mcp-MIZFVNCL.js.map → mcp-VMKWB2BY.js.map} +0 -0
  29. /package/dist/{vite-plugin-ST4JZQRJ.js.map → vite-plugin-BNI7YLCD.js.map} +0 -0
@@ -386,10 +386,20 @@ function createAppClient(baseUrlOrOptions, legacyFetchImpl) {
386
386
 
387
387
  // src/client/consume-ui-message-stream.ts
388
388
  async function consumeUIMessageStream(response, onMessage) {
389
- if (response.body === null) return;
390
- const { parseJsonEventStream, readUIMessageStream, uiMessageChunkSchema } = await import("ai");
389
+ const chunkStream = await responseToChunkStream(response);
390
+ await consumeChunkStream(chunkStream, onMessage);
391
+ }
392
+ async function responseToChunkStream(response) {
393
+ if (response.body === null) {
394
+ return new ReadableStream({
395
+ start(controller) {
396
+ controller.close();
397
+ }
398
+ });
399
+ }
400
+ const { parseJsonEventStream, uiMessageChunkSchema } = await import("ai");
391
401
  const parsed = parseJsonEventStream({ stream: response.body, schema: uiMessageChunkSchema });
392
- const chunkStream = new ReadableStream({
402
+ return new ReadableStream({
393
403
  async start(controller) {
394
404
  for await (const result of parsed) {
395
405
  if (result.success) controller.enqueue(result.value);
@@ -397,77 +407,401 @@ async function consumeUIMessageStream(response, onMessage) {
397
407
  controller.close();
398
408
  }
399
409
  });
400
- for await (const message of readUIMessageStream({ stream: chunkStream })) {
410
+ }
411
+ async function consumeChunkStream(stream, onMessage) {
412
+ const { readUIMessageStream } = await import("ai");
413
+ for await (const message of readUIMessageStream({ stream })) {
401
414
  onMessage(message);
402
415
  }
403
416
  }
404
417
 
405
418
  // src/client/use-agent.ts
406
- import { useCallback, useRef, useState } from "react";
407
- function useAgent(path, options = {}) {
408
- const [messages, setMessages] = useState([]);
409
- const [status, setStatus] = useState("idle");
410
- const [error, setError] = useState(void 0);
411
- const controllerRef = useRef(null);
412
- const abort = useCallback(() => {
413
- controllerRef.current?.abort();
414
- controllerRef.current = null;
415
- }, []);
416
- const reset = useCallback(() => {
417
- abort();
418
- setMessages([]);
419
- setError(void 0);
420
- setStatus("idle");
421
- }, [abort]);
422
- const send = useCallback(
423
- (input) => {
424
- abort();
425
- const controller = new AbortController();
426
- controllerRef.current = controller;
427
- setMessages([]);
428
- setError(void 0);
429
- setStatus("streaming");
430
- const fetchImpl = options.fetch ?? globalThis.fetch;
431
- void (async () => {
432
- try {
433
- const response = await fetchImpl(path, {
434
- method: "POST",
435
- headers: {
436
- "content-type": "application/json",
437
- accept: "text/event-stream",
438
- "X-Theo-Action": "1",
439
- ...options.headers
440
- },
441
- body: JSON.stringify(input),
442
- signal: controller.signal
443
- });
444
- await consumeUIMessageStream(response, (message) => {
445
- setMessages((prev) => {
446
- const next = [...prev];
447
- const idx = next.findIndex((m) => m.id === message.id);
448
- if (idx >= 0) next[idx] = message;
449
- else next.push(message);
450
- return next;
451
- });
452
- });
453
- setStatus("done");
454
- } catch (err) {
455
- if (controller.signal.aborted) return;
456
- setError(err instanceof Error ? err : new Error(String(err)));
457
- setStatus("error");
419
+ import { useMemo, useRef, useSyncExternalStore } from "react";
420
+
421
+ // src/client/agent-client.ts
422
+ function inputToText(input) {
423
+ if (typeof input === "object" && input !== null && typeof input.message === "string") {
424
+ return input.message;
425
+ }
426
+ if (typeof input === "string") return input;
427
+ return JSON.stringify(input);
428
+ }
429
+ function buildUserMessage(input) {
430
+ return {
431
+ id: crypto.randomUUID(),
432
+ role: "user",
433
+ parts: [{ type: "text", text: inputToText(input) }]
434
+ };
435
+ }
436
+ var AgentClient = class {
437
+ #transport;
438
+ #chatId = crypto.randomUUID();
439
+ #listeners = /* @__PURE__ */ new Set();
440
+ #messages = [];
441
+ #status = "idle";
442
+ #error;
443
+ #controller = null;
444
+ #snapshot = { messages: [], status: "idle", error: void 0 };
445
+ constructor(transport) {
446
+ this.#transport = transport;
447
+ }
448
+ /** Subscribe to state changes; returns an unsubscribe fn. */
449
+ subscribe = (listener) => {
450
+ this.#listeners.add(listener);
451
+ return () => {
452
+ this.#listeners.delete(listener);
453
+ };
454
+ };
455
+ /** The current immutable snapshot (stable reference until the next emit). */
456
+ getSnapshot = () => this.#snapshot;
457
+ #emit() {
458
+ this.#snapshot = { messages: this.#messages, status: this.#status, error: this.#error };
459
+ for (const listener of this.#listeners) listener();
460
+ }
461
+ #upsert(message) {
462
+ const next = [...this.#messages];
463
+ const idx = next.findIndex((existing) => existing.id === message.id);
464
+ if (idx >= 0) next[idx] = message;
465
+ else next.push(message);
466
+ this.#messages = next;
467
+ }
468
+ async #drive(open, controller) {
469
+ const aborted = () => controller.signal.aborted;
470
+ try {
471
+ const stream = await open();
472
+ if (aborted()) return;
473
+ if (stream === null) {
474
+ this.#status = this.#messages.length > 0 ? "done" : "idle";
475
+ this.#emit();
476
+ return;
477
+ }
478
+ await consumeChunkStream(stream, (message) => {
479
+ if (aborted()) return;
480
+ this.#upsert(message);
481
+ this.#emit();
482
+ });
483
+ if (aborted()) return;
484
+ this.#status = "done";
485
+ this.#emit();
486
+ } catch (err) {
487
+ if (aborted()) return;
488
+ this.#error = err instanceof Error ? err : new Error(String(err));
489
+ this.#status = "error";
490
+ this.#emit();
491
+ }
492
+ }
493
+ /** Send a typed input; opens a fresh stream (replaces prior messages). */
494
+ send = (input) => {
495
+ this.abort();
496
+ const controller = new AbortController();
497
+ this.#controller = controller;
498
+ this.#messages = [];
499
+ this.#error = void 0;
500
+ this.#status = "streaming";
501
+ this.#emit();
502
+ void this.#drive(
503
+ () => this.#transport.sendMessages({
504
+ trigger: "submit-message",
505
+ chatId: this.#chatId,
506
+ messageId: void 0,
507
+ messages: [buildUserMessage(input)],
508
+ abortSignal: controller.signal,
509
+ // Only object inputs flow as the request `body` (the turn text is always in `messages`);
510
+ // a primitive input is carried by the user message, never spread into the body.
511
+ body: typeof input === "object" && input !== null ? input : void 0
512
+ }),
513
+ controller
514
+ );
515
+ };
516
+ /** Resume an interrupted stream via the transport's `reconnectToStream` (no-op when unavailable). */
517
+ reconnect = () => {
518
+ const controller = new AbortController();
519
+ this.#controller = controller;
520
+ this.#status = "streaming";
521
+ this.#emit();
522
+ void this.#drive(() => this.#transport.reconnectToStream({ chatId: this.#chatId }), controller);
523
+ };
524
+ /** Abort an in-flight stream (not an error — leaves messages as-is). */
525
+ abort = () => {
526
+ this.#controller?.abort();
527
+ this.#controller = null;
528
+ };
529
+ /** Clear messages + error, back to idle. */
530
+ reset = () => {
531
+ this.abort();
532
+ this.#messages = [];
533
+ this.#error = void 0;
534
+ this.#status = "idle";
535
+ this.#emit();
536
+ };
537
+ /** Settle a paused HITL approval via the transport's HITL path (HTTP POST or inline callback). */
538
+ approve = async (approvalId, decision) => {
539
+ await this.#transport.approve?.(approvalId, decision);
540
+ };
541
+ };
542
+
543
+ // src/client/http-transport.ts
544
+ function toRecord(headers) {
545
+ if (headers === void 0) return {};
546
+ if (headers instanceof Headers) return Object.fromEntries(headers.entries());
547
+ return headers;
548
+ }
549
+ var HttpTransport = class {
550
+ #api;
551
+ #headers;
552
+ #fetch;
553
+ /** Server-minted id of the last run (from `x-theokit-run-id`) — the reconnect key. */
554
+ #lastRunId;
555
+ constructor(options) {
556
+ this.#api = options.api;
557
+ this.#headers = options.headers ?? {};
558
+ this.#fetch = options.fetch ?? globalThis.fetch;
559
+ }
560
+ /** Resolve the configured headers per request (a resolver evaluates now — dynamic auth is never stale). */
561
+ #resolveHeaders() {
562
+ return (typeof this.#headers === "function" ? this.#headers() : this.#headers) ?? {};
563
+ }
564
+ async sendMessages(options) {
565
+ const { messages, abortSignal, headers, body } = options;
566
+ const extra = typeof body === "object" ? body : void 0;
567
+ const response = await this.#fetch(this.#api, {
568
+ method: "POST",
569
+ headers: {
570
+ "content-type": "application/json",
571
+ accept: "text/event-stream",
572
+ "X-Theo-Action": "1",
573
+ ...this.#resolveHeaders(),
574
+ ...toRecord(headers)
575
+ },
576
+ body: JSON.stringify({ ...extra, messages }),
577
+ signal: abortSignal
578
+ });
579
+ if (!response.ok) {
580
+ throw new Error(
581
+ `Agent request to ${this.#api} failed: ${response.status} ${response.statusText}`
582
+ );
583
+ }
584
+ this.#lastRunId = response.headers.get("x-theokit-run-id") ?? void 0;
585
+ return responseToChunkStream(response);
586
+ }
587
+ async reconnectToStream(options) {
588
+ if (this.#lastRunId === void 0) return null;
589
+ const response = await this.#fetch(`${this.#api}/runs/${this.#lastRunId}/stream`, {
590
+ method: "GET",
591
+ headers: { ...this.#resolveHeaders(), ...toRecord(options.headers) }
592
+ });
593
+ if (response.status === 404) return null;
594
+ if (!response.ok) {
595
+ throw new Error(
596
+ `Agent reconnect to run ${this.#lastRunId} failed: ${response.status} ${response.statusText}`
597
+ );
598
+ }
599
+ return responseToChunkStream(response);
600
+ }
601
+ async approve(approvalId, decision) {
602
+ const response = await this.#fetch(`${this.#api}/approve/${approvalId}`, {
603
+ method: "POST",
604
+ headers: {
605
+ "content-type": "application/json",
606
+ "X-Theo-Action": "1",
607
+ ...this.#resolveHeaders()
608
+ },
609
+ body: JSON.stringify(decision)
610
+ });
611
+ if (!response.ok) {
612
+ throw new Error(`Approve ${approvalId} failed: ${response.status} ${response.statusText}`);
613
+ }
614
+ }
615
+ };
616
+
617
+ // src/client/use-agent.ts
618
+ function useAgent(pathOrTransport, options = {}) {
619
+ const optionsRef = useRef(options);
620
+ optionsRef.current = options;
621
+ const client = useMemo(
622
+ () => new AgentClient(
623
+ typeof pathOrTransport === "string" ? new HttpTransport({
624
+ api: pathOrTransport,
625
+ headers: () => optionsRef.current.headers,
626
+ fetch: optionsRef.current.fetch
627
+ }) : pathOrTransport
628
+ ),
629
+ // Identity is the binding; headers are resolved live via optionsRef, fetch captured at creation.
630
+ [pathOrTransport]
631
+ );
632
+ const state = useSyncExternalStore(client.subscribe, client.getSnapshot, client.getSnapshot);
633
+ return {
634
+ messages: state.messages,
635
+ status: state.status,
636
+ error: state.error,
637
+ send: client.send,
638
+ abort: client.abort,
639
+ reset: client.reset,
640
+ approve: client.approve,
641
+ reconnect: client.reconnect
642
+ };
643
+ }
644
+
645
+ // src/client/last-user-text.ts
646
+ function extractLastUserText(messages) {
647
+ for (let i = messages.length - 1; i >= 0; i--) {
648
+ const message = messages[i];
649
+ if (message.role !== "user") continue;
650
+ const text = message.parts.filter((part) => part.type === "text").map((part) => part.text).join("");
651
+ if (text.length > 0) return text;
652
+ }
653
+ return "";
654
+ }
655
+
656
+ // src/client/in-process-transport.ts
657
+ function generatorToStream(gen) {
658
+ return new ReadableStream({
659
+ async pull(controller) {
660
+ try {
661
+ const result = await gen.next();
662
+ if (result.done === true) {
663
+ controller.close();
664
+ return;
458
665
  }
459
- })();
666
+ controller.enqueue(result.value);
667
+ } catch (err) {
668
+ controller.error(err);
669
+ }
460
670
  },
461
- [abort, options.fetch, options.headers, path]
462
- );
463
- return { messages, status, error, send, abort, reset };
671
+ async cancel() {
672
+ await gen.return(void 0);
673
+ }
674
+ });
464
675
  }
676
+ var InProcessTransport = class {
677
+ #run;
678
+ /** Pending inline approvals: approvalId → resolver of the parked `awaitApproval` promise. */
679
+ #pending = /* @__PURE__ */ new Map();
680
+ constructor(options) {
681
+ this.#run = options.run;
682
+ }
683
+ #awaitApproval = (req) => new Promise((resolve, reject) => {
684
+ if (this.#pending.has(req.approvalId)) {
685
+ reject(new Error(`Duplicate pending approval id '${req.approvalId}' \u2014 ids must be unique.`));
686
+ return;
687
+ }
688
+ this.#pending.set(req.approvalId, resolve);
689
+ });
690
+ sendMessages(options) {
691
+ const { messages, abortSignal } = options;
692
+ const generator = this.#run({
693
+ message: extractLastUserText(messages),
694
+ signal: abortSignal ?? void 0,
695
+ awaitApproval: this.#awaitApproval
696
+ });
697
+ return Promise.resolve(generatorToStream(generator));
698
+ }
699
+ reconnectToStream() {
700
+ return Promise.resolve(null);
701
+ }
702
+ approve(approvalId, decision) {
703
+ const resolve = this.#pending.get(approvalId);
704
+ if (resolve === void 0) {
705
+ return Promise.reject(
706
+ new Error(`No pending approval '${approvalId}' (unknown or already settled).`)
707
+ );
708
+ }
709
+ this.#pending.delete(approvalId);
710
+ resolve(decision);
711
+ return Promise.resolve();
712
+ }
713
+ };
714
+
715
+ // src/client/channel-transport.ts
716
+ var ChannelTransport = class {
717
+ #source;
718
+ constructor(options) {
719
+ this.#source = options.source;
720
+ }
721
+ sendMessages(options) {
722
+ const { messages, abortSignal } = options;
723
+ const message = extractLastUserText(messages);
724
+ const source = this.#source;
725
+ let closed = false;
726
+ let teardown = () => void 0;
727
+ let detachAbort = () => void 0;
728
+ const stream = new ReadableStream({
729
+ start(controller) {
730
+ const finish = (settle) => {
731
+ if (closed) return;
732
+ closed = true;
733
+ detachAbort();
734
+ settle();
735
+ };
736
+ teardown = source.start(
737
+ { message },
738
+ {
739
+ onLine: (line) => {
740
+ if (closed) return;
741
+ let parsed;
742
+ try {
743
+ parsed = JSON.parse(line);
744
+ } catch {
745
+ return;
746
+ }
747
+ if (typeof parsed !== "object" || parsed === null || typeof parsed.type !== "string") {
748
+ return;
749
+ }
750
+ controller.enqueue(parsed);
751
+ },
752
+ onClose: () => {
753
+ finish(() => {
754
+ controller.close();
755
+ });
756
+ },
757
+ onError: (err) => {
758
+ finish(() => {
759
+ controller.error(err);
760
+ });
761
+ }
762
+ }
763
+ );
764
+ if (abortSignal !== void 0) {
765
+ const onAbort = () => {
766
+ finish(() => {
767
+ teardown();
768
+ controller.close();
769
+ });
770
+ };
771
+ if (abortSignal.aborted) onAbort();
772
+ else {
773
+ abortSignal.addEventListener("abort", onAbort);
774
+ detachAbort = () => {
775
+ abortSignal.removeEventListener("abort", onAbort);
776
+ };
777
+ }
778
+ }
779
+ },
780
+ cancel() {
781
+ if (closed) return;
782
+ closed = true;
783
+ detachAbort();
784
+ teardown();
785
+ }
786
+ });
787
+ return Promise.resolve(stream);
788
+ }
789
+ reconnectToStream() {
790
+ return Promise.resolve(null);
791
+ }
792
+ async approve(approvalId, decision) {
793
+ if (this.#source.settle === void 0) {
794
+ throw new Error(`This channel source has no HITL settle \u2014 cannot approve '${approvalId}'.`);
795
+ }
796
+ await this.#source.settle(approvalId, decision);
797
+ }
798
+ };
465
799
 
466
800
  // src/client/link.tsx
467
801
  import {
468
802
  Link as RouterLink
469
803
  } from "react-router";
470
- import { useRef as useRef2, useCallback as useCallback2, useEffect } from "react";
804
+ import { useRef as useRef2, useCallback, useEffect } from "react";
471
805
  import { jsx } from "react/jsx-runtime";
472
806
  var prefetched = /* @__PURE__ */ new Set();
473
807
  function injectPrefetch(href) {
@@ -485,7 +819,7 @@ function resolveTo(to) {
485
819
  }
486
820
  function Link({ prefetch = "intent", to, onMouseEnter, onFocus, ...rest }) {
487
821
  const ref = useRef2(null);
488
- const handleIntent = useCallback2(
822
+ const handleIntent = useCallback(
489
823
  (event) => {
490
824
  if (prefetch === "intent") {
491
825
  injectPrefetch(resolveTo(to));
@@ -601,17 +935,23 @@ function mountMcpApp(container, resource, opts) {
601
935
  };
602
936
  }
603
937
  export {
938
+ AgentClient,
939
+ ChannelTransport,
940
+ HttpTransport,
604
941
  Image,
942
+ InProcessTransport,
605
943
  Link,
606
944
  MCP_APP_SANDBOX,
607
945
  Metadata,
608
946
  TheoFetchError,
609
947
  buildUseTheoQueryConfig,
948
+ consumeChunkStream,
610
949
  consumeUIMessageStream,
611
950
  createAppClient,
612
951
  createBatcher,
613
952
  createGuestMessageHandler,
614
953
  mountMcpApp,
954
+ responseToChunkStream,
615
955
  stableQueryKey,
616
956
  theoFetch,
617
957
  useAgent