@dudousxd/nestjs-agent-react 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.
package/dist/markdown.cjs CHANGED
@@ -1,11 +1,8 @@
1
1
  "use strict";
2
- var __create = Object.create;
3
2
  var __defProp = Object.defineProperty;
4
3
  var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
4
  var __getOwnPropNames = Object.getOwnPropertyNames;
6
- var __getProtoOf = Object.getPrototypeOf;
7
5
  var __hasOwnProp = Object.prototype.hasOwnProperty;
8
- var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
9
6
  var __export = (target, all) => {
10
7
  for (var name in all)
11
8
  __defProp(target, name, { get: all[name], enumerable: true });
@@ -18,14 +15,6 @@ var __copyProps = (to, from, except, desc) => {
18
15
  }
19
16
  return to;
20
17
  };
21
- var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
22
- // If the importer is in node compatibility mode or this is not an ESM
23
- // file that has been converted to a CommonJS file using a Babel-
24
- // compatible transform (i.e. "__esModule" has not been set), then set
25
- // "default" to the CommonJS "module.exports" for node compatibility.
26
- isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
27
- mod
28
- ));
29
18
  var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
30
19
 
31
20
  // src/markdown/index.ts
@@ -41,33 +30,31 @@ var import_styles = require("streamdown/styles.css");
41
30
  var import_code = require("@streamdown/code");
42
31
  var import_math = require("@streamdown/math");
43
32
  var import_mermaid = require("@streamdown/mermaid");
44
- var import_react = __toESM(require("react"), 1);
45
33
  var import_streamdown = require("streamdown");
46
- var STREAMDOWN_PLUGINS = {
47
- code: import_code.code,
48
- mermaid: import_mermaid.mermaid,
49
- math: import_math.math
50
- };
51
- function AgentMarkdown({ children, isStreaming = false, remarkPlugins, components, allowedTags, literalTagContent }) {
52
- return /* @__PURE__ */ import_react.default.createElement(import_streamdown.Streamdown, {
53
- animated: true,
54
- isAnimating: isStreaming,
55
- plugins: STREAMDOWN_PLUGINS,
56
- ...remarkPlugins !== void 0 ? {
57
- remarkPlugins
58
- } : {},
59
- ...components !== void 0 ? {
60
- components
61
- } : {},
62
- ...allowedTags !== void 0 ? {
63
- allowedTags
64
- } : {},
65
- ...literalTagContent !== void 0 ? {
66
- literalTagContent
67
- } : {}
68
- }, children);
34
+ var import_jsx_runtime = require("react/jsx-runtime");
35
+ var STREAMDOWN_PLUGINS = { code: import_code.code, mermaid: import_mermaid.mermaid, math: import_math.math };
36
+ function AgentMarkdown({
37
+ children,
38
+ isStreaming = false,
39
+ remarkPlugins,
40
+ components,
41
+ allowedTags,
42
+ literalTagContent
43
+ }) {
44
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
45
+ import_streamdown.Streamdown,
46
+ {
47
+ animated: true,
48
+ isAnimating: isStreaming,
49
+ plugins: STREAMDOWN_PLUGINS,
50
+ ...remarkPlugins !== void 0 ? { remarkPlugins } : {},
51
+ ...components !== void 0 ? { components } : {},
52
+ ...allowedTags !== void 0 ? { allowedTags } : {},
53
+ ...literalTagContent !== void 0 ? { literalTagContent } : {},
54
+ children
55
+ }
56
+ );
69
57
  }
70
- __name(AgentMarkdown, "AgentMarkdown");
71
58
  // Annotate the CommonJS export names for ESM import in node:
72
59
  0 && (module.exports = {
73
60
  AgentMarkdown
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/markdown/index.ts","../src/markdown/agent-markdown.tsx"],"sourcesContent":["export { AgentMarkdown, type AgentMarkdownProps } from './agent-markdown.js';\n","import 'katex/dist/katex.min.css';\nimport 'streamdown/styles.css';\nimport { code } from '@streamdown/code';\nimport { math } from '@streamdown/math';\nimport { mermaid } from '@streamdown/mermaid';\n// React must be in scope: the test transform (unplugin-swc) uses the classic JSX runtime,\n// matching the other components in this package (e.g. chat-input.tsx).\nimport React, { type ComponentProps } from 'react';\nimport { Streamdown } from 'streamdown';\n\ntype StreamdownProps = ComponentProps<typeof Streamdown>;\n\nexport interface AgentMarkdownProps {\n /** The markdown source (typically the streaming assistant text). */\n children: string;\n /**\n * True while the surrounding chat believes the model is still streaming this message. Streamdown\n * uses the hint to defer expensive renders (Mermaid, syntax highlighting) until the block settles.\n */\n isStreaming?: boolean;\n /**\n * Extra remark plugins. This is the seam for app-specific markdown — e.g. flip splices its\n * `@base:/@user:` mention chips in via a remark plugin + a matching `components` entry here,\n * keeping that domain logic in the app rather than the library.\n */\n remarkPlugins?: StreamdownProps['remarkPlugins'];\n /** Custom element renderers (keyed by tag name), paired with `remarkPlugins`/`allowedTags`. */\n components?: StreamdownProps['components'];\n /** Custom tags (and their attributes) to keep through Streamdown's hardened sanitize schema. */\n allowedTags?: Record<string, string[]>;\n /** Tags whose children Streamdown should keep as literal text (not re-parse as markdown). */\n literalTagContent?: string[];\n}\n\nconst STREAMDOWN_PLUGINS = { code, mermaid, math } as const;\n\n/**\n * The optional rich markdown renderer for agent chat — the exact streamdown stack flip uses\n * (GFM, KaTeX math, syntax-highlighted code, Mermaid), ported 1:1 but with the flip-specific\n * mention handling lifted out into the `remarkPlugins`/`components` props so the library stays\n * domain-agnostic. Lives behind the `@dudousxd/nestjs-agent-react/markdown` subpath so the base\n * package never forces these heavy peers on consumers that don't want them.\n *\n * Pass it to a `MessageItem`/`MessageList` `renderText` slot:\n * `renderText={(text, { isStreaming }) => <AgentMarkdown isStreaming={isStreaming}>{text}</AgentMarkdown>}`\n */\nexport function AgentMarkdown({\n children,\n isStreaming = false,\n remarkPlugins,\n components,\n allowedTags,\n literalTagContent,\n}: AgentMarkdownProps) {\n return (\n <Streamdown\n animated\n isAnimating={isStreaming}\n plugins={STREAMDOWN_PLUGINS}\n {...(remarkPlugins !== undefined ? { remarkPlugins } : {})}\n {...(components !== undefined ? { components } : {})}\n {...(allowedTags !== undefined ? { allowedTags } : {})}\n {...(literalTagContent !== undefined ? { literalTagContent } : {})}\n >\n {children}\n </Streamdown>\n );\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA;;;;;;;ACAA,uBAAO;AACP,oBAAO;AACP,kBAAqB;AACrB,kBAAqB;AACrB,qBAAwB;AAGxB,mBAA2C;AAC3C,wBAA2B;AA0B3B,IAAMA,qBAAqB;EAAEC;EAAMC;EAASC;AAAK;AAY1C,SAASC,cAAc,EAC5BC,UACAC,cAAc,OACdC,eACAC,YACAC,aACAC,kBAAiB,GACE;AACnB,SACE,6BAAAC,QAAA,cAACC,8BAAAA;IACCC,UAAAA;IACAC,aAAaR;IACbS,SAASf;IACR,GAAIO,kBAAkBS,SAAY;MAAET;IAAc,IAAI,CAAC;IACvD,GAAIC,eAAeQ,SAAY;MAAER;IAAW,IAAI,CAAC;IACjD,GAAIC,gBAAgBO,SAAY;MAAEP;IAAY,IAAI,CAAC;IACnD,GAAIC,sBAAsBM,SAAY;MAAEN;IAAkB,IAAI,CAAC;KAE/DL,QAAAA;AAGP;AArBgBD;","names":["STREAMDOWN_PLUGINS","code","mermaid","math","AgentMarkdown","children","isStreaming","remarkPlugins","components","allowedTags","literalTagContent","React","Streamdown","animated","isAnimating","plugins","undefined"]}
1
+ {"version":3,"sources":["../src/markdown/index.ts","../src/markdown/agent-markdown.tsx"],"sourcesContent":["export { AgentMarkdown, type AgentMarkdownProps } from './agent-markdown.js';\n","import 'katex/dist/katex.min.css';\nimport 'streamdown/styles.css';\nimport { code } from '@streamdown/code';\nimport { math } from '@streamdown/math';\nimport { mermaid } from '@streamdown/mermaid';\n// React must be in scope: the test transform (unplugin-swc) uses the classic JSX runtime,\n// matching the other components in this package (e.g. chat-input.tsx).\nimport React, { type ComponentProps } from 'react';\nimport { Streamdown } from 'streamdown';\n\ntype StreamdownProps = ComponentProps<typeof Streamdown>;\n\nexport interface AgentMarkdownProps {\n /** The markdown source (typically the streaming assistant text). */\n children: string;\n /**\n * True while the surrounding chat believes the model is still streaming this message. Streamdown\n * uses the hint to defer expensive renders (Mermaid, syntax highlighting) until the block settles.\n */\n isStreaming?: boolean;\n /**\n * Extra remark plugins. This is the seam for app-specific markdown — e.g. flip splices its\n * `@base:/@user:` mention chips in via a remark plugin + a matching `components` entry here,\n * keeping that domain logic in the app rather than the library.\n */\n remarkPlugins?: StreamdownProps['remarkPlugins'];\n /** Custom element renderers (keyed by tag name), paired with `remarkPlugins`/`allowedTags`. */\n components?: StreamdownProps['components'];\n /** Custom tags (and their attributes) to keep through Streamdown's hardened sanitize schema. */\n allowedTags?: Record<string, string[]>;\n /** Tags whose children Streamdown should keep as literal text (not re-parse as markdown). */\n literalTagContent?: string[];\n}\n\nconst STREAMDOWN_PLUGINS = { code, mermaid, math } as const;\n\n/**\n * The optional rich markdown renderer for agent chat — the exact streamdown stack flip uses\n * (GFM, KaTeX math, syntax-highlighted code, Mermaid), ported 1:1 but with the flip-specific\n * mention handling lifted out into the `remarkPlugins`/`components` props so the library stays\n * domain-agnostic. Lives behind the `@dudousxd/nestjs-agent-react/markdown` subpath so the base\n * package never forces these heavy peers on consumers that don't want them.\n *\n * Pass it to a `MessageItem`/`MessageList` `renderText` slot:\n * `renderText={(text, { isStreaming }) => <AgentMarkdown isStreaming={isStreaming}>{text}</AgentMarkdown>}`\n */\nexport function AgentMarkdown({\n children,\n isStreaming = false,\n remarkPlugins,\n components,\n allowedTags,\n literalTagContent,\n}: AgentMarkdownProps) {\n return (\n <Streamdown\n animated\n isAnimating={isStreaming}\n plugins={STREAMDOWN_PLUGINS}\n {...(remarkPlugins !== undefined ? { remarkPlugins } : {})}\n {...(components !== undefined ? { components } : {})}\n {...(allowedTags !== undefined ? { allowedTags } : {})}\n {...(literalTagContent !== undefined ? { literalTagContent } : {})}\n >\n {children}\n </Streamdown>\n );\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;;;ACAA,uBAAO;AACP,oBAAO;AACP,kBAAqB;AACrB,kBAAqB;AACrB,qBAAwB;AAIxB,wBAA2B;AA+CvB;AArBJ,IAAM,qBAAqB,EAAE,wBAAM,iCAAS,uBAAK;AAY1C,SAAS,cAAc;AAAA,EAC5B;AAAA,EACA,cAAc;AAAA,EACd;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,GAAuB;AACrB,SACE;AAAA,IAAC;AAAA;AAAA,MACC,UAAQ;AAAA,MACR,aAAa;AAAA,MACb,SAAS;AAAA,MACR,GAAI,kBAAkB,SAAY,EAAE,cAAc,IAAI,CAAC;AAAA,MACvD,GAAI,eAAe,SAAY,EAAE,WAAW,IAAI,CAAC;AAAA,MACjD,GAAI,gBAAgB,SAAY,EAAE,YAAY,IAAI,CAAC;AAAA,MACnD,GAAI,sBAAsB,SAAY,EAAE,kBAAkB,IAAI,CAAC;AAAA,MAE/D;AAAA;AAAA,EACH;AAEJ;","names":[]}
package/dist/markdown.js CHANGED
@@ -1,39 +1,34 @@
1
- var __defProp = Object.defineProperty;
2
- var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
3
-
4
1
  // src/markdown/agent-markdown.tsx
5
2
  import "katex/dist/katex.min.css";
6
3
  import "streamdown/styles.css";
7
4
  import { code } from "@streamdown/code";
8
5
  import { math } from "@streamdown/math";
9
6
  import { mermaid } from "@streamdown/mermaid";
10
- import React from "react";
11
7
  import { Streamdown } from "streamdown";
12
- var STREAMDOWN_PLUGINS = {
13
- code,
14
- mermaid,
15
- math
16
- };
17
- function AgentMarkdown({ children, isStreaming = false, remarkPlugins, components, allowedTags, literalTagContent }) {
18
- return /* @__PURE__ */ React.createElement(Streamdown, {
19
- animated: true,
20
- isAnimating: isStreaming,
21
- plugins: STREAMDOWN_PLUGINS,
22
- ...remarkPlugins !== void 0 ? {
23
- remarkPlugins
24
- } : {},
25
- ...components !== void 0 ? {
26
- components
27
- } : {},
28
- ...allowedTags !== void 0 ? {
29
- allowedTags
30
- } : {},
31
- ...literalTagContent !== void 0 ? {
32
- literalTagContent
33
- } : {}
34
- }, children);
8
+ import { jsx } from "react/jsx-runtime";
9
+ var STREAMDOWN_PLUGINS = { code, mermaid, math };
10
+ function AgentMarkdown({
11
+ children,
12
+ isStreaming = false,
13
+ remarkPlugins,
14
+ components,
15
+ allowedTags,
16
+ literalTagContent
17
+ }) {
18
+ return /* @__PURE__ */ jsx(
19
+ Streamdown,
20
+ {
21
+ animated: true,
22
+ isAnimating: isStreaming,
23
+ plugins: STREAMDOWN_PLUGINS,
24
+ ...remarkPlugins !== void 0 ? { remarkPlugins } : {},
25
+ ...components !== void 0 ? { components } : {},
26
+ ...allowedTags !== void 0 ? { allowedTags } : {},
27
+ ...literalTagContent !== void 0 ? { literalTagContent } : {},
28
+ children
29
+ }
30
+ );
35
31
  }
36
- __name(AgentMarkdown, "AgentMarkdown");
37
32
  export {
38
33
  AgentMarkdown
39
34
  };
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/markdown/agent-markdown.tsx"],"sourcesContent":["import 'katex/dist/katex.min.css';\nimport 'streamdown/styles.css';\nimport { code } from '@streamdown/code';\nimport { math } from '@streamdown/math';\nimport { mermaid } from '@streamdown/mermaid';\n// React must be in scope: the test transform (unplugin-swc) uses the classic JSX runtime,\n// matching the other components in this package (e.g. chat-input.tsx).\nimport React, { type ComponentProps } from 'react';\nimport { Streamdown } from 'streamdown';\n\ntype StreamdownProps = ComponentProps<typeof Streamdown>;\n\nexport interface AgentMarkdownProps {\n /** The markdown source (typically the streaming assistant text). */\n children: string;\n /**\n * True while the surrounding chat believes the model is still streaming this message. Streamdown\n * uses the hint to defer expensive renders (Mermaid, syntax highlighting) until the block settles.\n */\n isStreaming?: boolean;\n /**\n * Extra remark plugins. This is the seam for app-specific markdown — e.g. flip splices its\n * `@base:/@user:` mention chips in via a remark plugin + a matching `components` entry here,\n * keeping that domain logic in the app rather than the library.\n */\n remarkPlugins?: StreamdownProps['remarkPlugins'];\n /** Custom element renderers (keyed by tag name), paired with `remarkPlugins`/`allowedTags`. */\n components?: StreamdownProps['components'];\n /** Custom tags (and their attributes) to keep through Streamdown's hardened sanitize schema. */\n allowedTags?: Record<string, string[]>;\n /** Tags whose children Streamdown should keep as literal text (not re-parse as markdown). */\n literalTagContent?: string[];\n}\n\nconst STREAMDOWN_PLUGINS = { code, mermaid, math } as const;\n\n/**\n * The optional rich markdown renderer for agent chat — the exact streamdown stack flip uses\n * (GFM, KaTeX math, syntax-highlighted code, Mermaid), ported 1:1 but with the flip-specific\n * mention handling lifted out into the `remarkPlugins`/`components` props so the library stays\n * domain-agnostic. Lives behind the `@dudousxd/nestjs-agent-react/markdown` subpath so the base\n * package never forces these heavy peers on consumers that don't want them.\n *\n * Pass it to a `MessageItem`/`MessageList` `renderText` slot:\n * `renderText={(text, { isStreaming }) => <AgentMarkdown isStreaming={isStreaming}>{text}</AgentMarkdown>}`\n */\nexport function AgentMarkdown({\n children,\n isStreaming = false,\n remarkPlugins,\n components,\n allowedTags,\n literalTagContent,\n}: AgentMarkdownProps) {\n return (\n <Streamdown\n animated\n isAnimating={isStreaming}\n plugins={STREAMDOWN_PLUGINS}\n {...(remarkPlugins !== undefined ? { remarkPlugins } : {})}\n {...(components !== undefined ? { components } : {})}\n {...(allowedTags !== undefined ? { allowedTags } : {})}\n {...(literalTagContent !== undefined ? { literalTagContent } : {})}\n >\n {children}\n </Streamdown>\n );\n}\n"],"mappings":";;;;AAAA,OAAO;AACP,OAAO;AACP,SAASA,YAAY;AACrB,SAASC,YAAY;AACrB,SAASC,eAAe;AAGxB,OAAOC,WAAoC;AAC3C,SAASC,kBAAkB;AA0B3B,IAAMC,qBAAqB;EAAEC;EAAMC;EAASC;AAAK;AAY1C,SAASC,cAAc,EAC5BC,UACAC,cAAc,OACdC,eACAC,YACAC,aACAC,kBAAiB,GACE;AACnB,SACE,sBAAA,cAACC,YAAAA;IACCC,UAAAA;IACAC,aAAaP;IACbQ,SAASd;IACR,GAAIO,kBAAkBQ,SAAY;MAAER;IAAc,IAAI,CAAC;IACvD,GAAIC,eAAeO,SAAY;MAAEP;IAAW,IAAI,CAAC;IACjD,GAAIC,gBAAgBM,SAAY;MAAEN;IAAY,IAAI,CAAC;IACnD,GAAIC,sBAAsBK,SAAY;MAAEL;IAAkB,IAAI,CAAC;KAE/DL,QAAAA;AAGP;AArBgBD;","names":["code","math","mermaid","React","Streamdown","STREAMDOWN_PLUGINS","code","mermaid","math","AgentMarkdown","children","isStreaming","remarkPlugins","components","allowedTags","literalTagContent","Streamdown","animated","isAnimating","plugins","undefined"]}
1
+ {"version":3,"sources":["../src/markdown/agent-markdown.tsx"],"sourcesContent":["import 'katex/dist/katex.min.css';\nimport 'streamdown/styles.css';\nimport { code } from '@streamdown/code';\nimport { math } from '@streamdown/math';\nimport { mermaid } from '@streamdown/mermaid';\n// React must be in scope: the test transform (unplugin-swc) uses the classic JSX runtime,\n// matching the other components in this package (e.g. chat-input.tsx).\nimport React, { type ComponentProps } from 'react';\nimport { Streamdown } from 'streamdown';\n\ntype StreamdownProps = ComponentProps<typeof Streamdown>;\n\nexport interface AgentMarkdownProps {\n /** The markdown source (typically the streaming assistant text). */\n children: string;\n /**\n * True while the surrounding chat believes the model is still streaming this message. Streamdown\n * uses the hint to defer expensive renders (Mermaid, syntax highlighting) until the block settles.\n */\n isStreaming?: boolean;\n /**\n * Extra remark plugins. This is the seam for app-specific markdown — e.g. flip splices its\n * `@base:/@user:` mention chips in via a remark plugin + a matching `components` entry here,\n * keeping that domain logic in the app rather than the library.\n */\n remarkPlugins?: StreamdownProps['remarkPlugins'];\n /** Custom element renderers (keyed by tag name), paired with `remarkPlugins`/`allowedTags`. */\n components?: StreamdownProps['components'];\n /** Custom tags (and their attributes) to keep through Streamdown's hardened sanitize schema. */\n allowedTags?: Record<string, string[]>;\n /** Tags whose children Streamdown should keep as literal text (not re-parse as markdown). */\n literalTagContent?: string[];\n}\n\nconst STREAMDOWN_PLUGINS = { code, mermaid, math } as const;\n\n/**\n * The optional rich markdown renderer for agent chat — the exact streamdown stack flip uses\n * (GFM, KaTeX math, syntax-highlighted code, Mermaid), ported 1:1 but with the flip-specific\n * mention handling lifted out into the `remarkPlugins`/`components` props so the library stays\n * domain-agnostic. Lives behind the `@dudousxd/nestjs-agent-react/markdown` subpath so the base\n * package never forces these heavy peers on consumers that don't want them.\n *\n * Pass it to a `MessageItem`/`MessageList` `renderText` slot:\n * `renderText={(text, { isStreaming }) => <AgentMarkdown isStreaming={isStreaming}>{text}</AgentMarkdown>}`\n */\nexport function AgentMarkdown({\n children,\n isStreaming = false,\n remarkPlugins,\n components,\n allowedTags,\n literalTagContent,\n}: AgentMarkdownProps) {\n return (\n <Streamdown\n animated\n isAnimating={isStreaming}\n plugins={STREAMDOWN_PLUGINS}\n {...(remarkPlugins !== undefined ? { remarkPlugins } : {})}\n {...(components !== undefined ? { components } : {})}\n {...(allowedTags !== undefined ? { allowedTags } : {})}\n {...(literalTagContent !== undefined ? { literalTagContent } : {})}\n >\n {children}\n </Streamdown>\n );\n}\n"],"mappings":";AAAA,OAAO;AACP,OAAO;AACP,SAAS,YAAY;AACrB,SAAS,YAAY;AACrB,SAAS,eAAe;AAIxB,SAAS,kBAAkB;AA+CvB;AArBJ,IAAM,qBAAqB,EAAE,MAAM,SAAS,KAAK;AAY1C,SAAS,cAAc;AAAA,EAC5B;AAAA,EACA,cAAc;AAAA,EACd;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,GAAuB;AACrB,SACE;AAAA,IAAC;AAAA;AAAA,MACC,UAAQ;AAAA,MACR,aAAa;AAAA,MACb,SAAS;AAAA,MACR,GAAI,kBAAkB,SAAY,EAAE,cAAc,IAAI,CAAC;AAAA,MACvD,GAAI,eAAe,SAAY,EAAE,WAAW,IAAI,CAAC;AAAA,MACjD,GAAI,gBAAgB,SAAY,EAAE,YAAY,IAAI,CAAC;AAAA,MACnD,GAAI,sBAAsB,SAAY,EAAE,kBAAkB,IAAI,CAAC;AAAA,MAE/D;AAAA;AAAA,EACH;AAEJ;","names":[]}
package/dist/media.cjs CHANGED
@@ -3,7 +3,6 @@ var __defProp = Object.defineProperty;
3
3
  var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
4
  var __getOwnPropNames = Object.getOwnPropertyNames;
5
5
  var __hasOwnProp = Object.prototype.hasOwnProperty;
6
- var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
7
6
  var __export = (target, all) => {
8
7
  for (var name in all)
9
8
  __defProp(target, name, { get: all[name], enumerable: true });
@@ -30,36 +29,20 @@ var import_nestjs_media_client = require("@dudousxd/nestjs-media-client");
30
29
 
31
30
  // src/http-error.ts
32
31
  function readErrorAnswer(text) {
33
- if (text == null || text === "") return {
34
- body: void 0,
35
- message: void 0,
36
- code: void 0
37
- };
32
+ if (text == null || text === "") return { body: void 0, message: void 0, code: void 0 };
38
33
  let body = text;
39
34
  try {
40
35
  body = JSON.parse(text);
41
36
  } catch {
42
- return {
43
- body: text,
44
- message: void 0,
45
- code: void 0
46
- };
37
+ return { body: text, message: void 0, code: void 0 };
47
38
  }
48
- if (body === null || typeof body !== "object") return {
49
- body,
50
- message: void 0,
51
- code: void 0
52
- };
39
+ if (body === null || typeof body !== "object")
40
+ return { body, message: void 0, code: void 0 };
53
41
  const record = body;
54
42
  const message = typeof record.message === "string" && record.message !== "" ? record.message : Array.isArray(record.message) && record.message.length > 0 && record.message.every((each) => typeof each === "string") ? record.message.join("; ") : void 0;
55
43
  const code = typeof record.code === "string" && record.code !== "" ? record.code : void 0;
56
- return {
57
- body,
58
- message,
59
- code
60
- };
44
+ return { body, message, code };
61
45
  }
62
- __name(readErrorAnswer, "readErrorAnswer");
63
46
  async function readErrorResponse(response) {
64
47
  let text;
65
48
  try {
@@ -69,7 +52,6 @@ async function readErrorResponse(response) {
69
52
  }
70
53
  return readErrorAnswer(text);
71
54
  }
72
- __name(readErrorResponse, "readErrorResponse");
73
55
  function reportHttpError(listener, error) {
74
56
  if (listener === void 0) return;
75
57
  try {
@@ -77,14 +59,12 @@ function reportHttpError(listener, error) {
77
59
  } catch {
78
60
  }
79
61
  }
80
- __name(reportHttpError, "reportHttpError");
81
62
 
82
63
  // src/client.ts
83
64
  function normalizeAgentPath(path) {
84
65
  const trimmed = (path ?? "agent").replace(/^\/+|\/+$/g, "");
85
66
  return trimmed === "" ? "" : `/${trimmed}`;
86
67
  }
87
- __name(normalizeAgentPath, "normalizeAgentPath");
88
68
 
89
69
  // src/media/index.ts
90
70
  function mediaAttachments(options = {}) {
@@ -94,78 +74,71 @@ function mediaAttachments(options = {}) {
94
74
  path: connection.path,
95
75
  getHeaders: connection.headers,
96
76
  fetch: connection.fetch,
97
- ...connection.credentials !== void 0 ? {
98
- credentials: connection.credentials
99
- } : {},
100
- ...connection.onHttpError !== void 0 ? {
101
- onHttpError: connection.onHttpError
102
- } : {}
77
+ ...connection.credentials !== void 0 ? { credentials: connection.credentials } : {},
78
+ ...connection.onHttpError !== void 0 ? { onHttpError: connection.onHttpError } : {}
103
79
  })(file, uploadOptions);
104
80
  }
105
- __name(mediaAttachments, "mediaAttachments");
106
81
  var MediaUploadError = class extends Error {
107
- static {
108
- __name(this, "MediaUploadError");
82
+ constructor(status, method, path, statusText, answer = { body: void 0, message: void 0, code: void 0 }) {
83
+ super(
84
+ answer.message ?? `Attachment upload failed: ${method} ${path} \u2192 ${status} ${statusText}`
85
+ );
86
+ this.status = status;
87
+ this.method = method;
88
+ this.path = path;
89
+ this.name = "MediaUploadError";
90
+ this.body = answer.body;
91
+ this.code = answer.code;
109
92
  }
110
93
  status;
111
94
  method;
112
95
  path;
113
96
  body;
114
97
  code;
115
- constructor(status, method, path, statusText, answer = {
116
- body: void 0,
117
- message: void 0,
118
- code: void 0
119
- }) {
120
- super(answer.message ?? `Attachment upload failed: ${method} ${path} \u2192 ${status} ${statusText}`), this.status = status, this.method = method, this.path = path;
121
- this.name = "MediaUploadError";
122
- this.body = answer.body;
123
- this.code = answer.code;
124
- }
125
98
  };
126
99
  function createMediaUpload(options = {}) {
127
100
  const origin = (options.baseUrl ?? "").replace(/\/$/, "");
128
101
  const base = `${origin}${normalizeAgentPath(options.path)}/attachments/uploads`;
129
- const baseFetch = /* @__PURE__ */ __name(() => options.fetch ?? fetch, "baseFetch");
130
- const fetchWithCredentials = /* @__PURE__ */ __name((input, init) => baseFetch()(input, {
102
+ const baseFetch = () => options.fetch ?? fetch;
103
+ const fetchWithCredentials = (input, init) => baseFetch()(input, {
131
104
  ...init,
132
- ...options.credentials !== void 0 ? {
133
- credentials: options.credentials
134
- } : {}
135
- }), "fetchWithCredentials");
136
- const headers = /* @__PURE__ */ __name(async () => ({
105
+ ...options.credentials !== void 0 ? { credentials: options.credentials } : {}
106
+ });
107
+ const headers = async () => ({
137
108
  ...options.headers,
138
109
  ...await options.getHeaders?.() ?? {}
139
- }), "headers");
140
- const discard = /* @__PURE__ */ __name((mediaId) => {
110
+ });
111
+ const discard = (mediaId) => {
141
112
  void (async () => {
142
113
  await fetchWithCredentials(`${base}/${encodeURIComponent(mediaId)}`, {
143
114
  method: "DELETE",
144
115
  headers: await headers()
145
116
  });
146
117
  })().catch(() => void 0);
147
- }, "discard");
148
- const call = /* @__PURE__ */ __name(async (method, url, body) => {
118
+ };
119
+ const call = async (method, url, body) => {
149
120
  const response = await fetchWithCredentials(url, {
150
121
  method,
151
122
  headers: {
152
123
  accept: "application/json",
153
- ...body !== void 0 ? {
154
- "content-type": "application/json"
155
- } : {},
124
+ ...body !== void 0 ? { "content-type": "application/json" } : {},
156
125
  ...await headers()
157
126
  },
158
- ...body !== void 0 ? {
159
- body: JSON.stringify(body)
160
- } : {}
127
+ ...body !== void 0 ? { body: JSON.stringify(body) } : {}
161
128
  });
162
129
  if (!response.ok) {
163
- const error = new MediaUploadError(response.status, method, url, response.statusText, await readErrorResponse(response));
130
+ const error = new MediaUploadError(
131
+ response.status,
132
+ method,
133
+ url,
134
+ response.statusText,
135
+ await readErrorResponse(response)
136
+ );
164
137
  reportHttpError(options.onHttpError, error);
165
138
  throw error;
166
139
  }
167
140
  return await response.json();
168
- }, "call");
141
+ };
169
142
  return async (file, { signal, onProgress } = {}) => {
170
143
  signal?.throwIfAborted();
171
144
  const begun = await call("POST", base, {
@@ -173,29 +146,24 @@ function createMediaUpload(options = {}) {
173
146
  contentType: file.type,
174
147
  size: file.size
175
148
  });
176
- const onAbort = /* @__PURE__ */ __name(() => discard(begun.mediaId), "onAbort");
177
- signal?.addEventListener("abort", onAbort, {
178
- once: true
179
- });
149
+ const onAbort = () => discard(begun.mediaId);
150
+ signal?.addEventListener("abort", onAbort, { once: true });
180
151
  try {
181
152
  const location = /^https?:\/\//.test(begun.location) ? begun.location : `${origin}${begun.location}`;
182
153
  await (0, import_nestjs_media_client.streamChunks)(location, file, {
183
154
  resume: false,
184
155
  fetchImpl: fetchWithCredentials,
185
156
  getHeaders: headers,
186
- ...options.chunkSize !== void 0 ? {
187
- chunkSize: options.chunkSize
188
- } : {},
189
- ...options.retries !== void 0 ? {
190
- retries: options.retries
191
- } : {},
192
- ...signal !== void 0 ? {
193
- signal
194
- } : {},
195
- onProgress: /* @__PURE__ */ __name((sent, total) => onProgress?.(total > 0 ? sent / total : 1), "onProgress")
157
+ ...options.chunkSize !== void 0 ? { chunkSize: options.chunkSize } : {},
158
+ ...options.retries !== void 0 ? { retries: options.retries } : {},
159
+ ...signal !== void 0 ? { signal } : {},
160
+ onProgress: (sent, total) => onProgress?.(total > 0 ? sent / total : 1)
196
161
  });
197
162
  signal?.throwIfAborted();
198
- const attachment = await call("POST", `${base}/${encodeURIComponent(begun.mediaId)}/complete`);
163
+ const attachment = await call(
164
+ "POST",
165
+ `${base}/${encodeURIComponent(begun.mediaId)}/complete`
166
+ );
199
167
  onProgress?.(1);
200
168
  return attachment;
201
169
  } catch (error) {
@@ -206,7 +174,6 @@ function createMediaUpload(options = {}) {
206
174
  }
207
175
  };
208
176
  }
209
- __name(createMediaUpload, "createMediaUpload");
210
177
  // Annotate the CommonJS export names for ESM import in node:
211
178
  0 && (module.exports = {
212
179
  MediaUploadError,
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/media/index.ts","../src/http-error.ts","../src/client.ts"],"sourcesContent":["import type { MessageAttachment } from '@dudousxd/nestjs-agent-core';\nimport { streamChunks } from '@dudousxd/nestjs-media-client';\nimport type { AttachmentUploadStrategy, UploadAttachmentOptions } from '../backend.js';\nimport { type AgentClientOptions, normalizeAgentPath } from '../client.js';\nimport {\n type AgentRequestError,\n type ErrorAnswer,\n readErrorResponse,\n reportHttpError,\n} from '../http-error.js';\n\n/** Tuning for {@link mediaAttachments}; every field optional. */\nexport interface MediaAttachmentsOptions {\n /** Bytes per tus `PATCH`. Default 5 MiB. */\n chunkSize?: number;\n /** Attempts per chunk before the upload fails. Default 3. */\n retries?: number;\n}\n\n/**\n * Resumable attachment uploads through `@dudousxd/nestjs-media`, in one line:\n *\n * ```tsx\n * <AgentProvider attachments={{ upload: mediaAttachments() }}>\n * ```\n *\n * Reuses the client's own connection (origin, path, headers, credentials), so it needs no\n * configuration. Also takes `new AgentClient({ attachments: { upload: mediaAttachments() } })`.\n */\nexport function mediaAttachments(options: MediaAttachmentsOptions = {}): AttachmentUploadStrategy {\n return (file, uploadOptions, connection) =>\n createMediaUpload({\n ...options,\n baseUrl: connection.baseUrl,\n path: connection.path,\n getHeaders: connection.headers,\n fetch: connection.fetch,\n ...(connection.credentials !== undefined ? { credentials: connection.credentials } : {}),\n ...(connection.onHttpError !== undefined ? { onHttpError: connection.onHttpError } : {}),\n })(file, uploadOptions);\n}\n\n/**\n * Where and how {@link createMediaUpload} talks to the server. The connection fields are\n * {@link AgentClientOptions}' — pass the same ones your `AgentClient` gets, so the tus requests\n * carry the same cookies / CSRF header / bearer token as the rest of the chat.\n */\nexport interface MediaUploadOptions extends AgentClientOptions {\n /** Bytes per tus `PATCH`. Default 5 MiB (nestjs-media-client's default). */\n chunkSize?: number;\n /** Attempts per chunk before the upload fails. Default 3. */\n retries?: number;\n}\n\n/** What `useAttachments({ upload })` and `AgentBackend.uploadAttachment` both take. */\nexport type AttachmentUpload = (\n file: File,\n options?: UploadAttachmentOptions,\n) => Promise<MessageAttachment>;\n\n/**\n * A non-2xx answer from the agent's upload routes. Carries what `AgentHttpError` does — `status`\n * (413 too large, 415 type refused, 404 not yours), the server's `message`, `code` and `body` —\n * as its own class because this subpath is bundled apart from the root entry, where an\n * `instanceof AgentHttpError` would not match a copy. Both satisfy `AgentRequestError`.\n */\nexport class MediaUploadError extends Error implements AgentRequestError {\n readonly body: unknown;\n readonly code: string | undefined;\n\n constructor(\n readonly status: number,\n readonly method: string,\n readonly path: string,\n statusText: string,\n answer: ErrorAnswer = { body: undefined, message: undefined, code: undefined },\n ) {\n super(\n answer.message ?? `Attachment upload failed: ${method} ${path} → ${status} ${statusText}`,\n );\n this.name = 'MediaUploadError';\n this.body = answer.body;\n this.code = answer.code;\n }\n}\n\ninterface BeginResponse {\n mediaId: string;\n location: string;\n}\n\n/**\n * A resumable `upload` for `useAttachments`, backed by `@dudousxd/nestjs-media` on the server\n * (`AgentMediaAttachmentsModule` from `@dudousxd/nestjs-agent/media`):\n *\n * 1. `POST <path>/attachments/uploads` — the agent validates the file and opens a tus session the\n * actor owns;\n * 2. the bytes stream to nestjs-media's own tus endpoint in chunks (`streamChunks`), reporting\n * progress and honouring the abort signal;\n * 3. `POST <path>/attachments/uploads/:mediaId/complete` — the agent confirms the bytes landed and\n * answers the `MessageAttachment` the turn will reference by `mediaId`.\n *\n * An aborted or failed upload is discarded server-side (`DELETE`), best effort.\n */\nexport function createMediaUpload(options: MediaUploadOptions = {}): AttachmentUpload {\n const origin = (options.baseUrl ?? '').replace(/\\/$/, '');\n const base = `${origin}${normalizeAgentPath(options.path)}/attachments/uploads`;\n const baseFetch = (): typeof fetch => options.fetch ?? fetch;\n // Every request — the agent's and media's tus PATCHes — rides the same credentials mode.\n const fetchWithCredentials: typeof fetch = (input, init) =>\n baseFetch()(input, {\n ...init,\n ...(options.credentials !== undefined ? { credentials: options.credentials } : {}),\n });\n const headers = async (): Promise<Record<string, string>> => ({\n ...options.headers,\n ...((await options.getHeaders?.()) ?? {}),\n });\n const discard = (mediaId: string): void => {\n void (async () => {\n await fetchWithCredentials(`${base}/${encodeURIComponent(mediaId)}`, {\n method: 'DELETE',\n headers: await headers(),\n });\n })().catch(() => undefined);\n };\n const call = async <T>(method: string, url: string, body?: unknown): Promise<T> => {\n const response = await fetchWithCredentials(url, {\n method,\n headers: {\n accept: 'application/json',\n ...(body !== undefined ? { 'content-type': 'application/json' } : {}),\n ...(await headers()),\n },\n ...(body !== undefined ? { body: JSON.stringify(body) } : {}),\n });\n if (!response.ok) {\n const error = new MediaUploadError(\n response.status,\n method,\n url,\n response.statusText,\n await readErrorResponse(response),\n );\n reportHttpError(options.onHttpError, error);\n throw error;\n }\n return (await response.json()) as T;\n };\n\n return async (file, { signal, onProgress } = {}) => {\n signal?.throwIfAborted();\n const begun = await call<BeginResponse>('POST', base, {\n filename: file.name,\n contentType: file.type,\n size: file.size,\n });\n const onAbort = () => discard(begun.mediaId);\n signal?.addEventListener('abort', onAbort, { once: true });\n try {\n const location = /^https?:\\/\\//.test(begun.location)\n ? begun.location\n : `${origin}${begun.location}`;\n await streamChunks(location, file, {\n resume: false,\n fetchImpl: fetchWithCredentials,\n getHeaders: headers,\n ...(options.chunkSize !== undefined ? { chunkSize: options.chunkSize } : {}),\n ...(options.retries !== undefined ? { retries: options.retries } : {}),\n ...(signal !== undefined ? { signal } : {}),\n onProgress: (sent, total) => onProgress?.(total > 0 ? sent / total : 1),\n });\n signal?.throwIfAborted();\n const attachment = await call<MessageAttachment>(\n 'POST',\n `${base}/${encodeURIComponent(begun.mediaId)}/complete`,\n );\n onProgress?.(1);\n return attachment;\n } catch (error) {\n if (!signal?.aborted) discard(begun.mediaId);\n throw error;\n } finally {\n signal?.removeEventListener('abort', onAbort);\n }\n };\n}\n","/**\n * What a server said when it refused a request, read once so every error class of this package\n * reports it the same way. Bundled into each entry that throws (the root and `/media`), which is\n * why it holds no class of its own.\n */\nexport interface ErrorAnswer {\n /** The body, parsed as JSON when it is JSON, else the raw text; `undefined` when empty. */\n body: unknown;\n /** `body.message` — a string, or NestJS's list of validation messages joined with `; `. */\n message: string | undefined;\n /** `body.code`, the machine-readable reason (`quota_exceeded`, …), when the server sent one. */\n code: string | undefined;\n}\n\n/** Read an error answer's body text. Never throws: a body that cannot be read is just absent. */\nexport function readErrorAnswer(text: string | null | undefined): ErrorAnswer {\n if (text == null || text === '') return { body: undefined, message: undefined, code: undefined };\n let body: unknown = text;\n try {\n body = JSON.parse(text);\n } catch {\n return { body: text, message: undefined, code: undefined };\n }\n if (body === null || typeof body !== 'object')\n return { body, message: undefined, code: undefined };\n const record = body as { message?: unknown; code?: unknown };\n const message =\n typeof record.message === 'string' && record.message !== ''\n ? record.message\n : Array.isArray(record.message) &&\n record.message.length > 0 &&\n record.message.every((each) => typeof each === 'string')\n ? record.message.join('; ')\n : undefined;\n const code = typeof record.code === 'string' && record.code !== '' ? record.code : undefined;\n return { body, message, code };\n}\n\n/** {@link readErrorAnswer} over a `Response` whose status is not 2xx. */\nexport async function readErrorResponse(response: Response): Promise<ErrorAnswer> {\n let text: string | undefined;\n try {\n text = await response.text();\n } catch {\n text = undefined;\n }\n return readErrorAnswer(text);\n}\n\n/**\n * The shape both {@link import('./client.js').AgentHttpError} and `MediaUploadError` share, for\n * code that handles either without an `instanceof` (the two live in separate bundles).\n */\nexport interface AgentRequestError extends Error {\n status: number;\n method: string;\n path: string;\n body: unknown;\n code: string | undefined;\n}\n\n/** Called with every error answer before it is thrown — for app-wide reactions (401, 402, …). */\nexport type HttpErrorListener = (error: AgentRequestError) => void;\n\n/** Tell `listener` about `error`; a listener that throws never replaces the error itself. */\nexport function reportHttpError(\n listener: HttpErrorListener | undefined,\n error: AgentRequestError,\n): void {\n if (listener === undefined) return;\n try {\n listener(error);\n } catch {\n /* the request's own error is what the caller gets */\n }\n}\n","import type {\n AgentCatalogEntry,\n AgentClientConfig,\n MessageAttachment,\n MessageFeedback,\n ModelCatalogView,\n QuotaReport,\n SkillCatalogEntry,\n ThreadDetail,\n ThreadSummary,\n ToolCatalogEntry,\n} from '@dudousxd/nestjs-agent-core';\nimport type {\n AgentBackend,\n AgentConnection,\n AttachmentUploadStrategy,\n ChatStreamRequest,\n ChatStreamResponse,\n MessageFeedbackInput,\n ResumeStreamRequest,\n ThreadPatch,\n UploadAttachmentOptions,\n} from './backend.js';\n\nimport {\n type AgentRequestError,\n type ErrorAnswer,\n type HttpErrorListener,\n readErrorAnswer,\n readErrorResponse,\n reportHttpError,\n} from './http-error.js';\n\nexport type { ThreadPatch } from './backend.js';\nexport type { AgentRequestError, HttpErrorListener } from './http-error.js';\n\n/**\n * Thrown by {@link AgentClient} on a non-2xx response. Carries the HTTP `status` so callers can\n * branch (e.g. 403 → \"not your thread\", 429 → quota) instead of string-matching a generic Error,\n * and what the server said: `message` is the body's `message` when it sent one (so a hook that\n * shows `error.message` shows the server's words), `code` its machine-readable `code`, and `body`\n * the whole answer, parsed when it is JSON.\n */\nexport class AgentHttpError extends Error implements AgentRequestError {\n /** The answer's body: parsed JSON, else its text; `undefined` when empty. */\n readonly body: unknown;\n /** The body's `code` (`quota_exceeded`, …), when the server sent one. */\n readonly code: string | undefined;\n\n constructor(\n readonly status: number,\n readonly method: string,\n readonly path: string,\n statusText: string,\n answer: ErrorAnswer = { body: undefined, message: undefined, code: undefined },\n ) {\n super(answer.message ?? `Agent request failed: ${method} ${path} → ${status} ${statusText}`);\n this.name = 'AgentHttpError';\n this.body = answer.body;\n this.code = answer.code;\n }\n\n /** Read a refused `response`'s body into an error. */\n static async from(response: Response, method: string, path: string): Promise<AgentHttpError> {\n return new AgentHttpError(\n response.status,\n method,\n path,\n response.statusText,\n await readErrorResponse(response),\n );\n }\n}\n\nexport interface CancelResult {\n aborted: boolean;\n}\n\nexport interface OkResult {\n ok: boolean;\n}\n\nexport interface AgentClientOptions {\n /**\n * The server's origin, e.g. `https://api.example.com`. Defaults to `''` (same origin). The\n * agent's route prefix is {@link AgentClientOptions.path}, not part of this.\n */\n baseUrl?: string;\n /**\n * The agent's route prefix — `AgentModule`'s `path`, with any global prefix in front\n * (`'api/agent'`). Leading/trailing slashes are optional. Defaults to `'agent'`.\n */\n path?: string;\n /** Static headers merged into every request. */\n headers?: Record<string, string>;\n /**\n * Resolved per request — for short-lived bearer tokens, or a CSRF header read from a cookie\n * (`{ 'X-XSRF-TOKEN': readCookie('XSRF-TOKEN') }`), which has to be read at request time because\n * the server may rotate it.\n */\n getHeaders?: () => Record<string, string> | Promise<Record<string, string>>;\n /**\n * Forwarded to fetch so cookie auth works. Same-origin requests send cookies by default; set\n * `'include'` when the API lives on another origin (and have it answer with credentialed CORS).\n */\n credentials?: RequestCredentials;\n /** Injectable for tests / non-browser runtimes. */\n fetch?: typeof fetch;\n /**\n * Called with every error answer (an {@link AgentHttpError}, or a `MediaUploadError` from a\n * `mediaAttachments()` upload) right before it is thrown — the place for app-wide reactions such\n * as \"401 → sign in again\". The error still reaches the caller. Not called for the `404` a\n * resume answers when nothing is streaming, which is an answer, not a failure.\n */\n onHttpError?: HttpErrorListener;\n /** Attachment uploads. */\n attachments?: {\n /**\n * How `uploadAttachment` uploads. Omitted → `POST <path>/attachments` (multipart). Pass\n * `mediaAttachments()` from `@dudousxd/nestjs-agent-react/media` for resumable uploads through\n * nestjs-media, or your own {@link AttachmentUploadStrategy}.\n */\n upload?: AttachmentUploadStrategy;\n };\n}\n\n/** `'/api/agent'` from `'api/agent'`, `'/api/agent/'`, …; `''` for an empty path. */\nexport function normalizeAgentPath(path: string | undefined): string {\n const trimmed = (path ?? 'agent').replace(/^\\/+|\\/+$/g, '');\n return trimmed === '' ? '' : `/${trimmed}`;\n}\n\nconst HEADER_RUN_ID = 'x-agent-run-id';\nconst HEADER_THREAD_ID = 'x-agent-thread-id';\n\n/**\n * Framework-agnostic REST client for the nestjs-agent endpoints — the default {@link AgentBackend}.\n * Used by `useAgentChat`, but standalone-usable (vanilla fetch, no React).\n */\nexport class AgentClient implements AgentBackend {\n constructor(private readonly options: AgentClientOptions = {}) {}\n\n /** `POST <path>/chat` → the turn's SSE stream. Throws {@link AgentHttpError} on a non-2xx. */\n async openChatStream(request: ChatStreamRequest): Promise<ChatStreamResponse> {\n const response = await this.fetchImpl()(`${this.root()}/chat`, {\n method: 'POST',\n headers: {\n 'content-type': 'application/json',\n accept: 'text/event-stream',\n ...(await this.resolveHeaders()),\n ...request.headers,\n },\n body: JSON.stringify(request.body),\n ...this.credentials(),\n ...(request.signal !== undefined ? { signal: request.signal } : {}),\n });\n if (!response.ok || !response.body) {\n throw await this.failure(response, 'POST', `${this.agentPath()}/chat`);\n }\n return streamResponse(response);\n }\n\n /**\n * `GET <path>/chat/:runId/stream[?after=<seq>]` → the run's SSE stream, or `null` when nothing is\n * streaming under that id (404).\n */\n async resumeChatStream(request: ResumeStreamRequest): Promise<ChatStreamResponse | null> {\n const path = `/chat/${encodeURIComponent(request.runId)}/stream`;\n const query = request.after !== undefined && request.after > 0 ? `?after=${request.after}` : '';\n const response = await this.fetchImpl()(`${this.root()}${path}${query}`, {\n method: 'GET',\n headers: {\n accept: 'text/event-stream',\n ...(await this.resolveHeaders()),\n ...request.headers,\n },\n ...this.credentials(),\n ...(request.signal !== undefined ? { signal: request.signal } : {}),\n });\n if (response.status === 404) {\n return null;\n }\n if (!response.ok || !response.body) {\n throw await this.failure(response, 'GET', `${this.agentPath()}${path}`);\n }\n return streamResponse(response);\n }\n\n /**\n * Rate a message (`'up'`/`'down'`, optional comment) or clear its rating (`value: null`). Answers\n * the stored rating.\n */\n setMessageFeedback(\n messageId: string,\n input: MessageFeedbackInput,\n ): Promise<{ feedback: MessageFeedback | null }> {\n return this.request<{ feedback: MessageFeedback | null }>(\n 'POST',\n `/messages/${encodeURIComponent(messageId)}/feedback`,\n input,\n );\n }\n\n listThreads(): Promise<ThreadSummary[]> {\n return this.request<ThreadSummary[]>('GET', '/threads');\n }\n\n /**\n * The skills this caller can invoke right now, scope-resolved — the same list, built by the same\n * call, that the model is offered, so what a user can type after a `/` and what the agent can\n * reach cannot drift apart. `threadId` reaches the host's own resolver, which may scope a skill to\n * one conversation; omitted, the server reads it as a brand-new thread.\n */\n listSkills(threadId?: string): Promise<SkillCatalogEntry[]> {\n const query = threadId === undefined ? '' : `?threadId=${encodeURIComponent(threadId)}`;\n return this.request<SkillCatalogEntry[]>('GET', `/skills${query}`);\n }\n\n /**\n * The tools this caller can reach through `agent` (the default agent when omitted), each with the\n * server-declared `presentation` a chat narrates it by — the same list the model is offered.\n * Prefer {@link useToolCatalog}, which fetches it once and shares it.\n */\n listTools(agent?: string): Promise<ToolCatalogEntry[]> {\n const query = agent === undefined ? '' : `?agent=${encodeURIComponent(agent)}`;\n return this.request<ToolCatalogEntry[]>('GET', `/tools${query}`);\n }\n\n getThread(id: string): Promise<ThreadDetail> {\n return this.request<ThreadDetail>('GET', `/threads/${encodeURIComponent(id)}`);\n }\n\n deleteThread(id: string): Promise<void> {\n return this.request<void>('DELETE', `/threads/${encodeURIComponent(id)}`);\n }\n\n forkFromMessage(threadId: string, messageId: string): Promise<ThreadSummary> {\n return this.request<ThreadSummary>(\n 'POST',\n `/threads/${encodeURIComponent(threadId)}/fork-from/${encodeURIComponent(messageId)}`,\n );\n }\n\n /** General `PATCH <path>/threads/:threadId` — title and/or the thread's pinned default agent. */\n updateThread(id: string, patch: ThreadPatch): Promise<OkResult> {\n return this.request<OkResult>('PATCH', `/threads/${encodeURIComponent(id)}`, patch);\n }\n\n /**\n * Uploads a file (image/PDF) for a vision-capable model turn. Multipart, field name `file` —\n * mirrors the backend's `POST <path>/attachments`. The returned {@link MessageAttachment} is\n * what a caller then rides on `sendMessage({ text }, { body: { attachments: [...] } })`.\n */\n async uploadAttachment(\n file: File,\n options: UploadAttachmentOptions = {},\n ): Promise<MessageAttachment> {\n const upload = this.options.attachments?.upload;\n if (upload !== undefined) {\n return upload(file, options, this.connection());\n }\n // `fetch` cannot observe an upload's progress; XHR can. Only when someone is listening, and\n // never when a `fetch` was injected (tests, non-browser runtimes).\n if (\n options.onProgress !== undefined &&\n this.options.fetch === undefined &&\n typeof XMLHttpRequest !== 'undefined'\n ) {\n return this.uploadWithProgress(file, options);\n }\n const formData = new FormData();\n formData.append('file', file);\n const response = await this.fetchImpl()(`${this.root()}/attachments`, {\n method: 'POST',\n headers: {\n accept: 'application/json',\n ...(await this.resolveHeaders()),\n },\n body: formData,\n ...this.credentials(),\n ...(options.signal !== undefined ? { signal: options.signal } : {}),\n });\n const attachment = await this.handleResponse<MessageAttachment>(\n response,\n 'POST',\n `${this.agentPath()}/attachments`,\n );\n options.onProgress?.(1);\n return attachment;\n }\n\n private async uploadWithProgress(\n file: File,\n { signal, onProgress }: UploadAttachmentOptions,\n ): Promise<MessageAttachment> {\n const headers: Record<string, string> = {\n accept: 'application/json',\n ...(await this.resolveHeaders()),\n };\n const url = `${this.root()}/attachments`;\n return new Promise<MessageAttachment>((resolve, reject) => {\n const xhr = new XMLHttpRequest();\n xhr.open('POST', url);\n for (const [name, value] of Object.entries(headers)) xhr.setRequestHeader(name, value);\n // Same-origin requests carry cookies regardless; this is the cross-origin opt-in.\n xhr.withCredentials = this.options.credentials === 'include';\n xhr.upload.onprogress = (event) => {\n if (event.lengthComputable && event.total > 0) onProgress?.(event.loaded / event.total);\n };\n xhr.onload = () => {\n if (xhr.status < 200 || xhr.status >= 300) {\n const error = new AgentHttpError(\n xhr.status,\n 'POST',\n `${this.agentPath()}/attachments`,\n xhr.statusText,\n readErrorAnswer(xhr.responseText),\n );\n reportHttpError(this.options.onHttpError, error);\n reject(error);\n return;\n }\n onProgress?.(1);\n resolve(JSON.parse(xhr.responseText) as MessageAttachment);\n };\n xhr.onerror = () => reject(new TypeError('Network error while uploading the attachment'));\n xhr.onabort = () => reject(new DOMException('The upload was aborted', 'AbortError'));\n if (signal !== undefined) {\n if (signal.aborted) {\n reject(new DOMException('The upload was aborted', 'AbortError'));\n return;\n }\n signal.addEventListener('abort', () => xhr.abort(), { once: true });\n }\n const formData = new FormData();\n formData.append('file', file);\n xhr.send(formData);\n });\n }\n\n promoteThread(id: string): Promise<OkResult> {\n return this.request<OkResult>('POST', `/threads/${encodeURIComponent(id)}/promote`);\n }\n\n truncateFromMessage(threadId: string, messageId: string): Promise<OkResult> {\n return this.request<OkResult>(\n 'DELETE',\n `/threads/${encodeURIComponent(threadId)}/from/${encodeURIComponent(messageId)}`,\n );\n }\n\n /** `GET <path>/models?agent=` — the models this caller may pick, grouped by provider. */\n listModels(agent?: string): Promise<ModelCatalogView> {\n const query = agent === undefined ? '' : `?agent=${encodeURIComponent(agent)}`;\n return this.request<ModelCatalogView>('GET', `/models${query}`);\n }\n\n /** `GET <path>/agents` — the registered agents, the default one flagged. */\n listAgents(): Promise<AgentCatalogEntry[]> {\n return this.request<AgentCatalogEntry[]>('GET', '/agents');\n }\n\n /** `GET <path>/config` — attachment limits and upload mode, and which features are on. */\n getConfig(): Promise<AgentClientConfig> {\n return this.request<AgentClientConfig>('GET', '/config');\n }\n\n /** `GET <path>/quota` — the caller's budget windows and the one blocking sends, if any. */\n getQuota(): Promise<QuotaReport> {\n return this.request<QuotaReport>('GET', '/quota');\n }\n\n cancelStream(runId: string): Promise<CancelResult> {\n return this.request<CancelResult>('POST', `/chat/${encodeURIComponent(runId)}/cancel`);\n }\n\n /**\n * `remember` approves later calls of the same tool in the same thread; `via` names the surface\n * the decision came through (the server records `'web'` when omitted).\n */\n approveToolCall(input: { toolCallId: string; remember?: boolean; via?: string }): Promise<void> {\n return this.request<void>('POST', '/tool-call/approve', input);\n }\n\n rejectToolCall(input: { toolCallId: string; reason?: string; via?: string }): Promise<void> {\n return this.request<void>('POST', '/tool-call/reject', input);\n }\n\n /**\n * Settle a parked question set. `answers` is questionId → chosen option values; a question left\n * out takes the pre-picked default the request carried, resolved server-side against the request\n * the run already holds. Omit the whole object and the user has confirmed every pre-picked\n * answer — which is the point of the surface, so it is a valid submission rather than a blank.\n * `via` names the surface the answer came through (the server records `'web'` when omitted).\n */\n answerToolCall(input: {\n toolCallId: string;\n answers?: Record<string, string[]>;\n via?: string;\n }): Promise<void> {\n return this.request<void>('POST', '/tool-call/answer', input);\n }\n\n /**\n * Decline to answer and let the agent proceed on its own pre-picked values. Lands on the same\n * values a confirmation would, and persists differently on purpose — only one of them is\n * evidence the user chose them.\n */\n skipToolCall(input: { toolCallId: string; via?: string }): Promise<void> {\n return this.request<void>('POST', '/tool-call/skip', input);\n }\n\n private fetchImpl(): typeof fetch {\n return this.options.fetch ?? globalThis.fetch;\n }\n\n /** This client's connection, for an {@link AttachmentUploadStrategy}. */\n private connection(): AgentConnection {\n return {\n baseUrl: this.baseUrl(),\n path: this.agentPath(),\n headers: () => this.resolveHeaders(),\n fetch: this.fetchImpl(),\n ...this.credentials(),\n ...(this.options.onHttpError !== undefined ? { onHttpError: this.options.onHttpError } : {}),\n };\n }\n\n private baseUrl(): string {\n return (this.options.baseUrl ?? '').replace(/\\/+$/, '');\n }\n\n private agentPath(): string {\n return normalizeAgentPath(this.options.path);\n }\n\n /** Origin + agent path: what every route hangs off. */\n private root(): string {\n return `${this.baseUrl()}${this.agentPath()}`;\n }\n\n private async resolveHeaders(): Promise<Record<string, string>> {\n const dynamic = (await this.options.getHeaders?.()) ?? {};\n return { ...this.options.headers, ...dynamic };\n }\n\n private credentials(): { credentials?: RequestCredentials } {\n return this.options.credentials !== undefined ? { credentials: this.options.credentials } : {};\n }\n\n private async request<T>(method: string, route: string, body?: unknown): Promise<T> {\n const path = `${this.agentPath()}${route}`;\n const response = await this.fetchImpl()(`${this.baseUrl()}${path}`, {\n method,\n headers: {\n accept: 'application/json',\n ...(body !== undefined ? { 'content-type': 'application/json' } : {}),\n ...(await this.resolveHeaders()),\n },\n ...(body !== undefined ? { body: JSON.stringify(body) } : {}),\n ...this.credentials(),\n });\n return this.handleResponse<T>(response, method, path);\n }\n\n /** The error for a refused `response`, already reported to `onHttpError`. */\n private async failure(response: Response, method: string, path: string): Promise<AgentHttpError> {\n const error = await AgentHttpError.from(response, method, path);\n reportHttpError(this.options.onHttpError, error);\n return error;\n }\n\n private async handleResponse<T>(response: Response, method: string, path: string): Promise<T> {\n if (!response.ok) {\n throw await this.failure(response, method, path);\n }\n if (response.status === 204) return undefined as T;\n const text = await response.text();\n if (!text) return undefined as T;\n return JSON.parse(text) as T;\n }\n}\n\nfunction streamResponse(response: Response): ChatStreamResponse {\n const body = response.body as ReadableStream<Uint8Array>;\n const runId = response.headers?.get(HEADER_RUN_ID) ?? undefined;\n const threadId = response.headers?.get(HEADER_THREAD_ID) ?? undefined;\n return {\n body,\n ...(runId ? { runId } : {}),\n ...(threadId ? { threadId } : {}),\n };\n}\n"],"mappings":"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","names":["readErrorAnswer","text","body","undefined","message","code","JSON","parse","record","Array","isArray","length","every","each","join","readErrorResponse","response","reportHttpError","listener","error","normalizeAgentPath","path","trimmed","replace","mediaAttachments","options","file","uploadOptions","connection","createMediaUpload","baseUrl","path","getHeaders","headers","fetch","credentials","undefined","onHttpError","MediaUploadError","Error","body","code","status","method","statusText","answer","message","name","origin","replace","base","normalizeAgentPath","baseFetch","fetchWithCredentials","input","init","discard","mediaId","encodeURIComponent","catch","call","url","response","accept","JSON","stringify","ok","error","readErrorResponse","reportHttpError","json","signal","onProgress","throwIfAborted","begun","filename","contentType","type","size","onAbort","addEventListener","once","location","test","streamChunks","resume","fetchImpl","chunkSize","retries","sent","total","attachment","aborted","removeEventListener"]}
1
+ {"version":3,"sources":["../src/media/index.ts","../src/http-error.ts","../src/client.ts"],"sourcesContent":["import type { MessageAttachment } from '@dudousxd/nestjs-agent-core';\nimport { streamChunks } from '@dudousxd/nestjs-media-client';\nimport type { AttachmentUploadStrategy, UploadAttachmentOptions } from '../backend.js';\nimport { type AgentClientOptions, normalizeAgentPath } from '../client.js';\nimport {\n type AgentRequestError,\n type ErrorAnswer,\n readErrorResponse,\n reportHttpError,\n} from '../http-error.js';\n\n/** Tuning for {@link mediaAttachments}; every field optional. */\nexport interface MediaAttachmentsOptions {\n /** Bytes per tus `PATCH`. Default 5 MiB. */\n chunkSize?: number;\n /** Attempts per chunk before the upload fails. Default 3. */\n retries?: number;\n}\n\n/**\n * Resumable attachment uploads through `@dudousxd/nestjs-media`, in one line:\n *\n * ```tsx\n * <AgentProvider attachments={{ upload: mediaAttachments() }}>\n * ```\n *\n * Reuses the client's own connection (origin, path, headers, credentials), so it needs no\n * configuration. Also takes `new AgentClient({ attachments: { upload: mediaAttachments() } })`.\n */\nexport function mediaAttachments(options: MediaAttachmentsOptions = {}): AttachmentUploadStrategy {\n return (file, uploadOptions, connection) =>\n createMediaUpload({\n ...options,\n baseUrl: connection.baseUrl,\n path: connection.path,\n getHeaders: connection.headers,\n fetch: connection.fetch,\n ...(connection.credentials !== undefined ? { credentials: connection.credentials } : {}),\n ...(connection.onHttpError !== undefined ? { onHttpError: connection.onHttpError } : {}),\n })(file, uploadOptions);\n}\n\n/**\n * Where and how {@link createMediaUpload} talks to the server. The connection fields are\n * {@link AgentClientOptions}' — pass the same ones your `AgentClient` gets, so the tus requests\n * carry the same cookies / CSRF header / bearer token as the rest of the chat.\n */\nexport interface MediaUploadOptions extends AgentClientOptions {\n /** Bytes per tus `PATCH`. Default 5 MiB (nestjs-media-client's default). */\n chunkSize?: number;\n /** Attempts per chunk before the upload fails. Default 3. */\n retries?: number;\n}\n\n/** What `useAttachments({ upload })` and `AgentBackend.uploadAttachment` both take. */\nexport type AttachmentUpload = (\n file: File,\n options?: UploadAttachmentOptions,\n) => Promise<MessageAttachment>;\n\n/**\n * A non-2xx answer from the agent's upload routes. Carries what `AgentHttpError` does — `status`\n * (413 too large, 415 type refused, 404 not yours), the server's `message`, `code` and `body` —\n * as its own class because this subpath is bundled apart from the root entry, where an\n * `instanceof AgentHttpError` would not match a copy. Both satisfy `AgentRequestError`.\n */\nexport class MediaUploadError extends Error implements AgentRequestError {\n readonly body: unknown;\n readonly code: string | undefined;\n\n constructor(\n readonly status: number,\n readonly method: string,\n readonly path: string,\n statusText: string,\n answer: ErrorAnswer = { body: undefined, message: undefined, code: undefined },\n ) {\n super(\n answer.message ?? `Attachment upload failed: ${method} ${path} → ${status} ${statusText}`,\n );\n this.name = 'MediaUploadError';\n this.body = answer.body;\n this.code = answer.code;\n }\n}\n\ninterface BeginResponse {\n mediaId: string;\n location: string;\n}\n\n/**\n * A resumable `upload` for `useAttachments`, backed by `@dudousxd/nestjs-media` on the server\n * (`AgentMediaAttachmentsModule` from `@dudousxd/nestjs-agent/media`):\n *\n * 1. `POST <path>/attachments/uploads` — the agent validates the file and opens a tus session the\n * actor owns;\n * 2. the bytes stream to nestjs-media's own tus endpoint in chunks (`streamChunks`), reporting\n * progress and honouring the abort signal;\n * 3. `POST <path>/attachments/uploads/:mediaId/complete` — the agent confirms the bytes landed and\n * answers the `MessageAttachment` the turn will reference by `mediaId`.\n *\n * An aborted or failed upload is discarded server-side (`DELETE`), best effort.\n */\nexport function createMediaUpload(options: MediaUploadOptions = {}): AttachmentUpload {\n const origin = (options.baseUrl ?? '').replace(/\\/$/, '');\n const base = `${origin}${normalizeAgentPath(options.path)}/attachments/uploads`;\n const baseFetch = (): typeof fetch => options.fetch ?? fetch;\n // Every request — the agent's and media's tus PATCHes — rides the same credentials mode.\n const fetchWithCredentials: typeof fetch = (input, init) =>\n baseFetch()(input, {\n ...init,\n ...(options.credentials !== undefined ? { credentials: options.credentials } : {}),\n });\n const headers = async (): Promise<Record<string, string>> => ({\n ...options.headers,\n ...((await options.getHeaders?.()) ?? {}),\n });\n const discard = (mediaId: string): void => {\n void (async () => {\n await fetchWithCredentials(`${base}/${encodeURIComponent(mediaId)}`, {\n method: 'DELETE',\n headers: await headers(),\n });\n })().catch(() => undefined);\n };\n const call = async <T>(method: string, url: string, body?: unknown): Promise<T> => {\n const response = await fetchWithCredentials(url, {\n method,\n headers: {\n accept: 'application/json',\n ...(body !== undefined ? { 'content-type': 'application/json' } : {}),\n ...(await headers()),\n },\n ...(body !== undefined ? { body: JSON.stringify(body) } : {}),\n });\n if (!response.ok) {\n const error = new MediaUploadError(\n response.status,\n method,\n url,\n response.statusText,\n await readErrorResponse(response),\n );\n reportHttpError(options.onHttpError, error);\n throw error;\n }\n return (await response.json()) as T;\n };\n\n return async (file, { signal, onProgress } = {}) => {\n signal?.throwIfAborted();\n const begun = await call<BeginResponse>('POST', base, {\n filename: file.name,\n contentType: file.type,\n size: file.size,\n });\n const onAbort = () => discard(begun.mediaId);\n signal?.addEventListener('abort', onAbort, { once: true });\n try {\n const location = /^https?:\\/\\//.test(begun.location)\n ? begun.location\n : `${origin}${begun.location}`;\n await streamChunks(location, file, {\n resume: false,\n fetchImpl: fetchWithCredentials,\n getHeaders: headers,\n ...(options.chunkSize !== undefined ? { chunkSize: options.chunkSize } : {}),\n ...(options.retries !== undefined ? { retries: options.retries } : {}),\n ...(signal !== undefined ? { signal } : {}),\n onProgress: (sent, total) => onProgress?.(total > 0 ? sent / total : 1),\n });\n signal?.throwIfAborted();\n const attachment = await call<MessageAttachment>(\n 'POST',\n `${base}/${encodeURIComponent(begun.mediaId)}/complete`,\n );\n onProgress?.(1);\n return attachment;\n } catch (error) {\n if (!signal?.aborted) discard(begun.mediaId);\n throw error;\n } finally {\n signal?.removeEventListener('abort', onAbort);\n }\n };\n}\n","/**\n * What a server said when it refused a request, read once so every error class of this package\n * reports it the same way. Bundled into each entry that throws (the root and `/media`), which is\n * why it holds no class of its own.\n */\nexport interface ErrorAnswer {\n /** The body, parsed as JSON when it is JSON, else the raw text; `undefined` when empty. */\n body: unknown;\n /** `body.message` — a string, or NestJS's list of validation messages joined with `; `. */\n message: string | undefined;\n /** `body.code`, the machine-readable reason (`quota_exceeded`, …), when the server sent one. */\n code: string | undefined;\n}\n\n/** Read an error answer's body text. Never throws: a body that cannot be read is just absent. */\nexport function readErrorAnswer(text: string | null | undefined): ErrorAnswer {\n if (text == null || text === '') return { body: undefined, message: undefined, code: undefined };\n let body: unknown = text;\n try {\n body = JSON.parse(text);\n } catch {\n return { body: text, message: undefined, code: undefined };\n }\n if (body === null || typeof body !== 'object')\n return { body, message: undefined, code: undefined };\n const record = body as { message?: unknown; code?: unknown };\n const message =\n typeof record.message === 'string' && record.message !== ''\n ? record.message\n : Array.isArray(record.message) &&\n record.message.length > 0 &&\n record.message.every((each) => typeof each === 'string')\n ? record.message.join('; ')\n : undefined;\n const code = typeof record.code === 'string' && record.code !== '' ? record.code : undefined;\n return { body, message, code };\n}\n\n/** {@link readErrorAnswer} over a `Response` whose status is not 2xx. */\nexport async function readErrorResponse(response: Response): Promise<ErrorAnswer> {\n let text: string | undefined;\n try {\n text = await response.text();\n } catch {\n text = undefined;\n }\n return readErrorAnswer(text);\n}\n\n/**\n * The shape both {@link import('./client.js').AgentHttpError} and `MediaUploadError` share, for\n * code that handles either without an `instanceof` (the two live in separate bundles).\n */\nexport interface AgentRequestError extends Error {\n status: number;\n method: string;\n path: string;\n body: unknown;\n code: string | undefined;\n}\n\n/** Called with every error answer before it is thrown — for app-wide reactions (401, 402, …). */\nexport type HttpErrorListener = (error: AgentRequestError) => void;\n\n/** Tell `listener` about `error`; a listener that throws never replaces the error itself. */\nexport function reportHttpError(\n listener: HttpErrorListener | undefined,\n error: AgentRequestError,\n): void {\n if (listener === undefined) return;\n try {\n listener(error);\n } catch {\n /* the request's own error is what the caller gets */\n }\n}\n","import type {\n AgentCatalogEntry,\n AgentClientConfig,\n ChatQueueState,\n MessageAttachment,\n MessageFeedback,\n ModelCatalogView,\n QuotaReport,\n SkillCatalogEntry,\n ThreadDetail,\n ThreadSummary,\n ToolCatalogEntry,\n} from '@dudousxd/nestjs-agent-core';\nimport type {\n AgentBackend,\n AgentConnection,\n AttachmentUploadStrategy,\n ChatStreamRequest,\n ChatStreamResponse,\n MessageFeedbackInput,\n QueuedMessageUpdate,\n QueuedSendResult,\n ResumeStreamRequest,\n ThreadPatch,\n UploadAttachmentOptions,\n} from './backend.js';\n\nimport {\n type AgentRequestError,\n type ErrorAnswer,\n type HttpErrorListener,\n readErrorAnswer,\n readErrorResponse,\n reportHttpError,\n} from './http-error.js';\n\nexport type { ThreadPatch } from './backend.js';\nexport type { AgentRequestError, HttpErrorListener } from './http-error.js';\n\n/**\n * Thrown by {@link AgentClient} on a non-2xx response. Carries the HTTP `status` so callers can\n * branch (e.g. 403 → \"not your thread\", 429 → quota) instead of string-matching a generic Error,\n * and what the server said: `message` is the body's `message` when it sent one (so a hook that\n * shows `error.message` shows the server's words), `code` its machine-readable `code`, and `body`\n * the whole answer, parsed when it is JSON.\n */\nexport class AgentHttpError extends Error implements AgentRequestError {\n /** The answer's body: parsed JSON, else its text; `undefined` when empty. */\n readonly body: unknown;\n /** The body's `code` (`quota_exceeded`, …), when the server sent one. */\n readonly code: string | undefined;\n\n constructor(\n readonly status: number,\n readonly method: string,\n readonly path: string,\n statusText: string,\n answer: ErrorAnswer = { body: undefined, message: undefined, code: undefined },\n ) {\n super(answer.message ?? `Agent request failed: ${method} ${path} → ${status} ${statusText}`);\n this.name = 'AgentHttpError';\n this.body = answer.body;\n this.code = answer.code;\n }\n\n /** Read a refused `response`'s body into an error. */\n static async from(response: Response, method: string, path: string): Promise<AgentHttpError> {\n return new AgentHttpError(\n response.status,\n method,\n path,\n response.statusText,\n await readErrorResponse(response),\n );\n }\n}\n\nexport interface CancelResult {\n aborted: boolean;\n}\n\nexport interface OkResult {\n ok: boolean;\n}\n\nexport interface AgentClientOptions {\n /**\n * The server's origin, e.g. `https://api.example.com`. Defaults to `''` (same origin). The\n * agent's route prefix is {@link AgentClientOptions.path}, not part of this.\n */\n baseUrl?: string;\n /**\n * The agent's route prefix — `AgentModule`'s `path`, with any global prefix in front\n * (`'api/agent'`). Leading/trailing slashes are optional. Defaults to `'agent'`.\n */\n path?: string;\n /** Static headers merged into every request. */\n headers?: Record<string, string>;\n /**\n * Resolved per request — for short-lived bearer tokens, or a CSRF header read from a cookie\n * (`{ 'X-XSRF-TOKEN': readCookie('XSRF-TOKEN') }`), which has to be read at request time because\n * the server may rotate it.\n */\n getHeaders?: () => Record<string, string> | Promise<Record<string, string>>;\n /**\n * Forwarded to fetch so cookie auth works. Same-origin requests send cookies by default; set\n * `'include'` when the API lives on another origin (and have it answer with credentialed CORS).\n */\n credentials?: RequestCredentials;\n /** Injectable for tests / non-browser runtimes. */\n fetch?: typeof fetch;\n /**\n * Called with every error answer (an {@link AgentHttpError}, or a `MediaUploadError` from a\n * `mediaAttachments()` upload) right before it is thrown — the place for app-wide reactions such\n * as \"401 → sign in again\". The error still reaches the caller. Not called for the `404` a\n * resume answers when nothing is streaming, which is an answer, not a failure.\n */\n onHttpError?: HttpErrorListener;\n /** Attachment uploads. */\n attachments?: {\n /**\n * How `uploadAttachment` uploads. Omitted → `POST <path>/attachments` (multipart). Pass\n * `mediaAttachments()` from `@dudousxd/nestjs-agent-react/media` for resumable uploads through\n * nestjs-media, or your own {@link AttachmentUploadStrategy}.\n */\n upload?: AttachmentUploadStrategy;\n };\n}\n\n/** `'/api/agent'` from `'api/agent'`, `'/api/agent/'`, …; `''` for an empty path. */\nexport function normalizeAgentPath(path: string | undefined): string {\n const trimmed = (path ?? 'agent').replace(/^\\/+|\\/+$/g, '');\n return trimmed === '' ? '' : `/${trimmed}`;\n}\n\nconst HEADER_RUN_ID = 'x-agent-run-id';\nconst HEADER_THREAD_ID = 'x-agent-thread-id';\n\n/**\n * Framework-agnostic REST client for the nestjs-agent endpoints — the default {@link AgentBackend}.\n * Used by `useAgentChat`, but standalone-usable (vanilla fetch, no React).\n */\nexport class AgentClient implements AgentBackend {\n constructor(private readonly options: AgentClientOptions = {}) {}\n\n /** `POST <path>/chat` → the turn's SSE stream. Throws {@link AgentHttpError} on a non-2xx. */\n async openChatStream(request: ChatStreamRequest): Promise<ChatStreamResponse> {\n const response = await this.fetchImpl()(`${this.root()}/chat`, {\n method: 'POST',\n headers: {\n 'content-type': 'application/json',\n accept: 'text/event-stream',\n ...(await this.resolveHeaders()),\n ...request.headers,\n },\n body: JSON.stringify(request.body),\n ...this.credentials(),\n ...(request.signal !== undefined ? { signal: request.signal } : {}),\n });\n if (response.status === 202) {\n // Queued behind a turn already running on the thread: JSON, not a stream.\n return { body: emptyStream(), queued: (await response.json()) as QueuedSendResult };\n }\n if (!response.ok || !response.body) {\n throw await this.failure(response, 'POST', `${this.agentPath()}/chat`);\n }\n return streamResponse(response);\n }\n\n /**\n * `POST <path>/chat` for a message that should wait in the thread's queue — the body carries\n * `mode: 'queue'` (or `'interrupt'`), which the server always answers with `202` JSON.\n */\n enqueueMessage(request: ChatStreamRequest): Promise<QueuedSendResult> {\n return this.request<QueuedSendResult>('POST', '/chat', {\n mode: 'queue',\n ...request.body,\n });\n }\n\n /** `GET <path>/threads/:id/queue` — the thread's waiting messages, and whether it drains. */\n getQueue(threadId: string): Promise<ChatQueueState> {\n return this.request<ChatQueueState>('GET', `/threads/${encodeURIComponent(threadId)}/queue`);\n }\n\n /** `PATCH <path>/queue/:messageId` — change a waiting message's text/attachments, or move it. */\n updateQueuedMessage(messageId: string, update: QueuedMessageUpdate): Promise<ChatQueueState> {\n return this.request<ChatQueueState>('PATCH', `/queue/${encodeURIComponent(messageId)}`, update);\n }\n\n /** `DELETE <path>/queue/:messageId`. */\n removeQueuedMessage(messageId: string): Promise<ChatQueueState> {\n return this.request<ChatQueueState>('DELETE', `/queue/${encodeURIComponent(messageId)}`);\n }\n\n /**\n * `POST <path>/queue/:messageId/interrupt` — run a waiting message now, cancelling the running\n * turn for it. `runId` when nothing was running and it started; else `interrupting`.\n */\n interruptQueuedMessage(\n messageId: string,\n ): Promise<ChatQueueState & { runId?: string; interrupting?: string }> {\n return this.request<ChatQueueState & { runId?: string; interrupting?: string }>(\n 'POST',\n `/queue/${encodeURIComponent(messageId)}/interrupt`,\n );\n }\n\n /** `DELETE <path>/threads/:id/queue` — drop every waiting message. */\n clearQueue(threadId: string): Promise<ChatQueueState> {\n return this.request<ChatQueueState>('DELETE', `/threads/${encodeURIComponent(threadId)}/queue`);\n }\n\n /** `POST <path>/threads/:id/queue/resume` — lift a pause; `runId` when the head started. */\n resumeQueue(threadId: string): Promise<ChatQueueState & { runId?: string }> {\n return this.request<ChatQueueState & { runId?: string }>(\n 'POST',\n `/threads/${encodeURIComponent(threadId)}/queue/resume`,\n );\n }\n\n /**\n * `GET <path>/chat/:runId/stream[?after=<seq>]` → the run's SSE stream, or `null` when nothing is\n * streaming under that id (404).\n */\n async resumeChatStream(request: ResumeStreamRequest): Promise<ChatStreamResponse | null> {\n const path = `/chat/${encodeURIComponent(request.runId)}/stream`;\n const query = request.after !== undefined && request.after > 0 ? `?after=${request.after}` : '';\n const response = await this.fetchImpl()(`${this.root()}${path}${query}`, {\n method: 'GET',\n headers: {\n accept: 'text/event-stream',\n ...(await this.resolveHeaders()),\n ...request.headers,\n },\n ...this.credentials(),\n ...(request.signal !== undefined ? { signal: request.signal } : {}),\n });\n if (response.status === 404) {\n return null;\n }\n if (!response.ok || !response.body) {\n throw await this.failure(response, 'GET', `${this.agentPath()}${path}`);\n }\n return streamResponse(response);\n }\n\n /**\n * Rate a message (`'up'`/`'down'`, optional comment) or clear its rating (`value: null`). Answers\n * the stored rating.\n */\n setMessageFeedback(\n messageId: string,\n input: MessageFeedbackInput,\n ): Promise<{ feedback: MessageFeedback | null }> {\n return this.request<{ feedback: MessageFeedback | null }>(\n 'POST',\n `/messages/${encodeURIComponent(messageId)}/feedback`,\n input,\n );\n }\n\n listThreads(): Promise<ThreadSummary[]> {\n return this.request<ThreadSummary[]>('GET', '/threads');\n }\n\n /**\n * The skills this caller can invoke right now, scope-resolved — the same list, built by the same\n * call, that the model is offered, so what a user can type after a `/` and what the agent can\n * reach cannot drift apart. `threadId` reaches the host's own resolver, which may scope a skill to\n * one conversation; omitted, the server reads it as a brand-new thread.\n */\n listSkills(threadId?: string): Promise<SkillCatalogEntry[]> {\n const query = threadId === undefined ? '' : `?threadId=${encodeURIComponent(threadId)}`;\n return this.request<SkillCatalogEntry[]>('GET', `/skills${query}`);\n }\n\n /**\n * The tools this caller can reach through `agent` (the default agent when omitted), each with the\n * server-declared `presentation` a chat narrates it by — the same list the model is offered.\n * Prefer {@link useToolCatalog}, which fetches it once and shares it.\n */\n listTools(agent?: string): Promise<ToolCatalogEntry[]> {\n const query = agent === undefined ? '' : `?agent=${encodeURIComponent(agent)}`;\n return this.request<ToolCatalogEntry[]>('GET', `/tools${query}`);\n }\n\n getThread(id: string): Promise<ThreadDetail> {\n return this.request<ThreadDetail>('GET', `/threads/${encodeURIComponent(id)}`);\n }\n\n deleteThread(id: string): Promise<void> {\n return this.request<void>('DELETE', `/threads/${encodeURIComponent(id)}`);\n }\n\n forkFromMessage(threadId: string, messageId: string): Promise<ThreadSummary> {\n return this.request<ThreadSummary>(\n 'POST',\n `/threads/${encodeURIComponent(threadId)}/fork-from/${encodeURIComponent(messageId)}`,\n );\n }\n\n /** General `PATCH <path>/threads/:threadId` — title and/or the thread's pinned default agent. */\n updateThread(id: string, patch: ThreadPatch): Promise<OkResult> {\n return this.request<OkResult>('PATCH', `/threads/${encodeURIComponent(id)}`, patch);\n }\n\n /**\n * Uploads a file (image/PDF) for a vision-capable model turn. Multipart, field name `file` —\n * mirrors the backend's `POST <path>/attachments`. The returned {@link MessageAttachment} is\n * what a caller then rides on `sendMessage({ text }, { body: { attachments: [...] } })`.\n */\n async uploadAttachment(\n file: File,\n options: UploadAttachmentOptions = {},\n ): Promise<MessageAttachment> {\n const upload = this.options.attachments?.upload;\n if (upload !== undefined) {\n return upload(file, options, this.connection());\n }\n // `fetch` cannot observe an upload's progress; XHR can. Only when someone is listening, and\n // never when a `fetch` was injected (tests, non-browser runtimes).\n if (\n options.onProgress !== undefined &&\n this.options.fetch === undefined &&\n typeof XMLHttpRequest !== 'undefined'\n ) {\n return this.uploadWithProgress(file, options);\n }\n const formData = new FormData();\n formData.append('file', file);\n const response = await this.fetchImpl()(`${this.root()}/attachments`, {\n method: 'POST',\n headers: {\n accept: 'application/json',\n ...(await this.resolveHeaders()),\n },\n body: formData,\n ...this.credentials(),\n ...(options.signal !== undefined ? { signal: options.signal } : {}),\n });\n const attachment = await this.handleResponse<MessageAttachment>(\n response,\n 'POST',\n `${this.agentPath()}/attachments`,\n );\n options.onProgress?.(1);\n return attachment;\n }\n\n private async uploadWithProgress(\n file: File,\n { signal, onProgress }: UploadAttachmentOptions,\n ): Promise<MessageAttachment> {\n const headers: Record<string, string> = {\n accept: 'application/json',\n ...(await this.resolveHeaders()),\n };\n const url = `${this.root()}/attachments`;\n return new Promise<MessageAttachment>((resolve, reject) => {\n const xhr = new XMLHttpRequest();\n xhr.open('POST', url);\n for (const [name, value] of Object.entries(headers)) xhr.setRequestHeader(name, value);\n // Same-origin requests carry cookies regardless; this is the cross-origin opt-in.\n xhr.withCredentials = this.options.credentials === 'include';\n xhr.upload.onprogress = (event) => {\n if (event.lengthComputable && event.total > 0) onProgress?.(event.loaded / event.total);\n };\n xhr.onload = () => {\n if (xhr.status < 200 || xhr.status >= 300) {\n const error = new AgentHttpError(\n xhr.status,\n 'POST',\n `${this.agentPath()}/attachments`,\n xhr.statusText,\n readErrorAnswer(xhr.responseText),\n );\n reportHttpError(this.options.onHttpError, error);\n reject(error);\n return;\n }\n onProgress?.(1);\n resolve(JSON.parse(xhr.responseText) as MessageAttachment);\n };\n xhr.onerror = () => reject(new TypeError('Network error while uploading the attachment'));\n xhr.onabort = () => reject(new DOMException('The upload was aborted', 'AbortError'));\n if (signal !== undefined) {\n if (signal.aborted) {\n reject(new DOMException('The upload was aborted', 'AbortError'));\n return;\n }\n signal.addEventListener('abort', () => xhr.abort(), { once: true });\n }\n const formData = new FormData();\n formData.append('file', file);\n xhr.send(formData);\n });\n }\n\n promoteThread(id: string): Promise<OkResult> {\n return this.request<OkResult>('POST', `/threads/${encodeURIComponent(id)}/promote`);\n }\n\n truncateFromMessage(threadId: string, messageId: string): Promise<OkResult> {\n return this.request<OkResult>(\n 'DELETE',\n `/threads/${encodeURIComponent(threadId)}/from/${encodeURIComponent(messageId)}`,\n );\n }\n\n /** `GET <path>/models?agent=` — the models this caller may pick, grouped by provider. */\n listModels(agent?: string): Promise<ModelCatalogView> {\n const query = agent === undefined ? '' : `?agent=${encodeURIComponent(agent)}`;\n return this.request<ModelCatalogView>('GET', `/models${query}`);\n }\n\n /** `GET <path>/agents` — the registered agents, the default one flagged. */\n listAgents(): Promise<AgentCatalogEntry[]> {\n return this.request<AgentCatalogEntry[]>('GET', '/agents');\n }\n\n /** `GET <path>/config` — attachment limits and upload mode, and which features are on. */\n getConfig(): Promise<AgentClientConfig> {\n return this.request<AgentClientConfig>('GET', '/config');\n }\n\n /** `GET <path>/quota` — the caller's budget windows and the one blocking sends, if any. */\n getQuota(): Promise<QuotaReport> {\n return this.request<QuotaReport>('GET', '/quota');\n }\n\n cancelStream(runId: string): Promise<CancelResult> {\n return this.request<CancelResult>('POST', `/chat/${encodeURIComponent(runId)}/cancel`);\n }\n\n /**\n * `remember` approves later calls of the same tool in the same thread; `via` names the surface\n * the decision came through (the server records `'web'` when omitted).\n */\n approveToolCall(input: { toolCallId: string; remember?: boolean; via?: string }): Promise<void> {\n return this.request<void>('POST', '/tool-call/approve', input);\n }\n\n rejectToolCall(input: { toolCallId: string; reason?: string; via?: string }): Promise<void> {\n return this.request<void>('POST', '/tool-call/reject', input);\n }\n\n /**\n * Settle a parked question set. `answers` is questionId → chosen option values; a question left\n * out takes the pre-picked default the request carried, resolved server-side against the request\n * the run already holds. Omit the whole object and the user has confirmed every pre-picked\n * answer — which is the point of the surface, so it is a valid submission rather than a blank.\n * `via` names the surface the answer came through (the server records `'web'` when omitted).\n */\n answerToolCall(input: {\n toolCallId: string;\n answers?: Record<string, string[]>;\n via?: string;\n }): Promise<void> {\n return this.request<void>('POST', '/tool-call/answer', input);\n }\n\n /**\n * Decline to answer and let the agent proceed on its own pre-picked values. Lands on the same\n * values a confirmation would, and persists differently on purpose — only one of them is\n * evidence the user chose them.\n */\n skipToolCall(input: { toolCallId: string; via?: string }): Promise<void> {\n return this.request<void>('POST', '/tool-call/skip', input);\n }\n\n private fetchImpl(): typeof fetch {\n return this.options.fetch ?? globalThis.fetch;\n }\n\n /** This client's connection, for an {@link AttachmentUploadStrategy}. */\n private connection(): AgentConnection {\n return {\n baseUrl: this.baseUrl(),\n path: this.agentPath(),\n headers: () => this.resolveHeaders(),\n fetch: this.fetchImpl(),\n ...this.credentials(),\n ...(this.options.onHttpError !== undefined ? { onHttpError: this.options.onHttpError } : {}),\n };\n }\n\n private baseUrl(): string {\n return (this.options.baseUrl ?? '').replace(/\\/+$/, '');\n }\n\n private agentPath(): string {\n return normalizeAgentPath(this.options.path);\n }\n\n /** Origin + agent path: what every route hangs off. */\n private root(): string {\n return `${this.baseUrl()}${this.agentPath()}`;\n }\n\n private async resolveHeaders(): Promise<Record<string, string>> {\n const dynamic = (await this.options.getHeaders?.()) ?? {};\n return { ...this.options.headers, ...dynamic };\n }\n\n private credentials(): { credentials?: RequestCredentials } {\n return this.options.credentials !== undefined ? { credentials: this.options.credentials } : {};\n }\n\n private async request<T>(method: string, route: string, body?: unknown): Promise<T> {\n const path = `${this.agentPath()}${route}`;\n const response = await this.fetchImpl()(`${this.baseUrl()}${path}`, {\n method,\n headers: {\n accept: 'application/json',\n ...(body !== undefined ? { 'content-type': 'application/json' } : {}),\n ...(await this.resolveHeaders()),\n },\n ...(body !== undefined ? { body: JSON.stringify(body) } : {}),\n ...this.credentials(),\n });\n return this.handleResponse<T>(response, method, path);\n }\n\n /** The error for a refused `response`, already reported to `onHttpError`. */\n private async failure(response: Response, method: string, path: string): Promise<AgentHttpError> {\n const error = await AgentHttpError.from(response, method, path);\n reportHttpError(this.options.onHttpError, error);\n return error;\n }\n\n private async handleResponse<T>(response: Response, method: string, path: string): Promise<T> {\n if (!response.ok) {\n throw await this.failure(response, method, path);\n }\n if (response.status === 204) return undefined as T;\n const text = await response.text();\n if (!text) return undefined as T;\n return JSON.parse(text) as T;\n }\n}\n\nfunction emptyStream(): ReadableStream<Uint8Array> {\n return new ReadableStream<Uint8Array>({\n start(controller) {\n controller.close();\n },\n });\n}\n\nfunction streamResponse(response: Response): ChatStreamResponse {\n const body = response.body as ReadableStream<Uint8Array>;\n const runId = response.headers?.get(HEADER_RUN_ID) ?? undefined;\n const threadId = response.headers?.get(HEADER_THREAD_ID) ?? undefined;\n return {\n body,\n ...(runId ? { runId } : {}),\n ...(threadId ? { threadId } : {}),\n };\n}\n"],"mappings":"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","names":[]}
package/dist/media.d.cts CHANGED
@@ -1,5 +1,5 @@
1
1
  import { MessageAttachment } from '@dudousxd/nestjs-agent-core';
2
- import { U as UploadAttachmentOptions, g as AgentRequestError, E as ErrorAnswer, d as AgentClientOptions, a as AttachmentUploadStrategy } from './client-D4ZSbZJ8.cjs';
2
+ import { U as UploadAttachmentOptions, g as AgentRequestError, E as ErrorAnswer, d as AgentClientOptions, a as AttachmentUploadStrategy } from './client-CKwSMitS.cjs';
3
3
 
4
4
  /** Tuning for {@link mediaAttachments}; every field optional. */
5
5
  interface MediaAttachmentsOptions {
package/dist/media.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import { MessageAttachment } from '@dudousxd/nestjs-agent-core';
2
- import { U as UploadAttachmentOptions, g as AgentRequestError, E as ErrorAnswer, d as AgentClientOptions, a as AttachmentUploadStrategy } from './client-D4ZSbZJ8.js';
2
+ import { U as UploadAttachmentOptions, g as AgentRequestError, E as ErrorAnswer, d as AgentClientOptions, a as AttachmentUploadStrategy } from './client-CKwSMitS.js';
3
3
 
4
4
  /** Tuning for {@link mediaAttachments}; every field optional. */
5
5
  interface MediaAttachmentsOptions {