@laintern/chat-sdk 0.1.0 → 0.1.1

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/README.md CHANGED
@@ -111,11 +111,28 @@ The SDK reads the JWT's `exp` and refreshes 60 seconds before it, via your endpo
111
111
 
112
112
  Pass `storage: sessionStorage` (or your own `{ getItem, setItem, removeItem }`) to survive reloads. Storage that throws — private mode, blocked cookies — is handled: the SDK just refetches.
113
113
 
114
+ ## Citations
115
+
116
+ The answer text carries `[1]`-style markers. Resolve them without writing a parser:
117
+
118
+ ```ts
119
+ import { segmentAnswer, isNavigable } from "@laintern/chat-sdk"
120
+
121
+ for (const segment of segmentAnswer(turn.assistantContent, turn)) {
122
+ if (segment.type === "text") render(segment.text)
123
+ else if (segment.source && isNavigable(segment.source)) renderLink(segment.source, segment.index)
124
+ else renderPlainMarker(segment.index) // knowledge-base passage, or not resolved yet
125
+ }
126
+ ```
127
+
128
+ Two things to expect. A source with an empty `url` is normal — a knowledge-base passage lives in the client's document store, not on the web; `isNavigable()` tells you, and `section` is the heading path inside the document. And a marker can render before its citation event arrives, because the answer streams token by token: `segmentAnswer` falls back to source order, and yields `source: null` when even that is not available yet.
129
+
114
130
  ## API
115
131
 
116
132
  - `createLainternClient(options)` → `sendMessage()` (async iterable of raw events), `getConfig()`, `submitFeedback()`, `tokens`
117
133
  - `createConversation(client, options?)` → `getState()`, `subscribe()`, `send()`, `abort()`, `reset()`, `setFeedback()`
118
134
  - `useLainternChat(client, options?)` — React binding, `@laintern/chat-sdk/react`
135
+ - `segmentAnswer(text, turn)`, `resolveCitation(index, turn)`, `isNavigable(source)` — citation helpers
119
136
  - `parseSSE(response)`, `createTokenManager(options)` — the building blocks, if you want to assemble your own
120
137
 
121
138
  `getConfig()` returns the agent's transparency disclaimer. If it is enabled, show it at the start of every conversation — that is an EU AI Act obligation on the deployer, not a decoration.
@@ -261,6 +261,49 @@ function applyFailure(patchLast, failure) {
261
261
  }));
262
262
  }
263
263
 
264
- export { LainternError, createConversation, errorFromResponse };
265
- //# sourceMappingURL=chunk-WMKNNVMS.js.map
266
- //# sourceMappingURL=chunk-WMKNNVMS.js.map
264
+ // src/citations.ts
265
+ var SOURCE_INDEX_RE = /^source(\d+)$/;
266
+ function sourceIndex(source) {
267
+ const match = source.content_id ? SOURCE_INDEX_RE.exec(source.content_id) : null;
268
+ return match ? Number.parseInt(match[1], 10) : Number.POSITIVE_INFINITY;
269
+ }
270
+ function fromSearchResult(source) {
271
+ return {
272
+ source_id: source.content_id || source.url,
273
+ title: source.title,
274
+ url: source.url,
275
+ content_type: source.content_type,
276
+ ...source.section ? { section: source.section } : {}
277
+ };
278
+ }
279
+ function resolveCitation(index, turn) {
280
+ const exact = turn.citations.get(index);
281
+ if (exact) return exact;
282
+ if (index < 1 || turn.sources.length === 0) return null;
283
+ const ordered = [...turn.sources].sort((a, b) => sourceIndex(a) - sourceIndex(b));
284
+ const fallback = ordered[index - 1];
285
+ return fallback ? fromSearchResult(fallback) : null;
286
+ }
287
+ function isNavigable(source) {
288
+ return typeof source.url === "string" && source.url.trim().length > 0;
289
+ }
290
+ var CITATION_PATTERN = /\[(\d+)\]/g;
291
+ function segmentAnswer(content, turn) {
292
+ const segments = [];
293
+ let lastIndex = 0;
294
+ for (const match of content.matchAll(CITATION_PATTERN)) {
295
+ const at = match.index ?? 0;
296
+ if (at > lastIndex) segments.push({ type: "text", text: content.slice(lastIndex, at) });
297
+ const index = Number.parseInt(match[1], 10);
298
+ segments.push({ type: "citation", index, source: resolveCitation(index, turn) });
299
+ lastIndex = at + match[0].length;
300
+ }
301
+ if (lastIndex < content.length) {
302
+ segments.push({ type: "text", text: content.slice(lastIndex) });
303
+ }
304
+ return segments;
305
+ }
306
+
307
+ export { LainternError, createConversation, errorFromResponse, isNavigable, resolveCitation, segmentAnswer };
308
+ //# sourceMappingURL=chunk-OSMAH2QV.js.map
309
+ //# sourceMappingURL=chunk-OSMAH2QV.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/errors.ts","../src/conversation.ts","../src/citations.ts"],"names":[],"mappings":";AAMO,IAAM,aAAA,GAAN,cAA4B,KAAA,CAAM;AAAA,EAC9B,IAAA;AAAA,EACA,MAAA;AAAA;AAAA,EAEA,cAAA;AAAA,EACA,eAAA;AAAA;AAAA,EAEA,MAAA;AAAA,EAET,WAAA,CACE,IAAA,EACA,OAAA,EACA,OAAA,GAMI,EAAC,EACL;AACA,IAAA,KAAA,CAAM,OAAA,EAAS,QAAQ,KAAA,KAAU,MAAA,GAAY,EAAE,KAAA,EAAO,OAAA,CAAQ,KAAA,EAAM,GAAI,MAAS,CAAA;AACjF,IAAA,IAAA,CAAK,IAAA,GAAO,eAAA;AACZ,IAAA,IAAA,CAAK,IAAA,GAAO,IAAA;AACZ,IAAA,IAAA,CAAK,MAAA,GAAS,QAAQ,MAAA,IAAU,IAAA;AAChC,IAAA,IAAI,OAAA,CAAQ,cAAA,EAAgB,IAAA,CAAK,cAAA,GAAiB,OAAA,CAAQ,cAAA;AAC1D,IAAA,IAAI,OAAA,CAAQ,eAAA,EAAiB,IAAA,CAAK,eAAA,GAAkB,OAAA,CAAQ,eAAA;AAC5D,IAAA,IAAI,OAAA,CAAQ,MAAA,EAAQ,IAAA,CAAK,MAAA,GAAS,OAAA,CAAQ,MAAA;AAAA,EAC5C;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,IAAI,WAAA,GAAuB;AACzB,IAAA,OAAO,IAAA,CAAK,IAAA,KAAS,qBAAA,IAAyB,IAAA,CAAK,MAAA,KAAW,WAAA;AAAA,EAChE;AACF;AAEA,SAAS,IAAA,CAAuB,OAAgB,OAAA,EAAsC;AACpF,EAAA,OAAO,OAAO,KAAA,KAAU,QAAA,IAAa,QAA8B,QAAA,CAAS,KAAK,IAC5E,KAAA,GACD,MAAA;AACN;AAGA,eAAsB,kBAAkB,QAAA,EAA4C;AAClF,EAAA,IAAI,IAAA,GAAO,CAAA,KAAA,EAAQ,QAAA,CAAS,MAAM,CAAA,CAAA;AAClC,EAAA,IAAI,OAAA,GAAU,+BAAA;AACd,EAAA,IAAI,cAAA;AACJ,EAAA,IAAI,eAAA;AACJ,EAAA,IAAI,MAAA;AAEJ,EAAA,IAAI;AACF,IAAA,MAAM,IAAA,GAAQ,MAAM,QAAA,CAAS,IAAA,EAAK;AAUlC,IAAA,IAAI,IAAA,EAAM,OAAO,IAAA,EAAM;AACrB,MAAA,IAAA,GAAO,KAAK,KAAA,CAAM,IAAA;AAClB,MAAA,OAAA,GAAU,IAAA,CAAK,MAAM,OAAA,IAAW,OAAA;AAChC,MAAA,cAAA,GAAiB,KAAK,IAAA,CAAK,KAAA,CAAM,OAAO,CAAC,MAAA,EAAQ,OAAO,CAAU,CAAA;AAClE,MAAA,eAAA,GAAkB,IAAA,CAAK,KAAK,KAAA,CAAM,MAAA,EAAQ,CAAC,QAAA,EAAU,MAAA,EAAQ,KAAK,CAAU,CAAA;AAC5E,MAAA,IAAI,OAAO,IAAA,CAAK,KAAA,CAAM,WAAW,QAAA,EAAU,MAAA,GAAS,KAAK,KAAA,CAAM,MAAA;AAAA,IACjE,CAAA,MAAA,IAAW,OAAO,IAAA,EAAM,OAAA,KAAY,QAAA,EAAU;AAC5C,MAAA,OAAA,GAAU,IAAA,CAAK,OAAA;AAAA,IACjB;AAAA,EACF,CAAA,CAAA,MAAQ;AAAA,EAER;AAEA,EAAA,OAAO,IAAI,aAAA,CAAc,IAAA,EAAM,OAAA,EAAS;AAAA,IACtC,QAAQ,QAAA,CAAS,MAAA;AAAA,IACjB,cAAA;AAAA,IACA,eAAA;AAAA,IACA;AAAA,GACD,CAAA;AACH;;;ACLA,SAAS,KAAA,GAAgB;AACvB,EAAA,MAAM,IAAI,UAAA,CAAW,MAAA;AACrB,EAAA,IAAI,KAAK,OAAO,CAAA,CAAE,eAAe,UAAA,EAAY,OAAO,EAAE,UAAA,EAAW;AACjE,EAAA,OAAO,CAAA,KAAA,EAAQ,IAAA,CAAK,GAAA,EAAK,IAAI,IAAA,CAAK,MAAA,EAAO,CAAE,QAAA,CAAS,EAAE,CAAA,CAAE,KAAA,CAAM,CAAA,EAAG,EAAE,CAAC,CAAA,CAAA;AACtE;AAEA,SAAS,UAAA,CAAW,SAAiB,MAAA,EAAyB;AAC5D,EAAA,OAAO;AAAA,IACL,IAAI,KAAA,EAAM;AAAA,IACV,WAAA,EAAa,OAAA;AAAA,IACb,GAAI,MAAA,EAAQ,MAAA,GAAS,EAAE,UAAA,EAAY,MAAA,KAAW,EAAC;AAAA,IAC/C,gBAAA,EAAkB,EAAA;AAAA,IAClB,SAAA,sBAAe,GAAA,EAAI;AAAA,IACnB,SAAS,EAAC;AAAA,IACV,iBAAiB,EAAC;AAAA,IAClB,IAAA,EAAM,IAAA;AAAA,IACN,OAAO,EAAC;AAAA,IACR,WAAA,EAAa;AAAA,GACf;AACF;AAcO,SAAS,kBAAA,CACd,MAAA,EACA,OAAA,GAA+B,EAAC,EAClB;AACd,EAAA,IAAI,KAAA,GAA2B;AAAA,IAC7B,KAAA,EAAO,OAAA,CAAQ,YAAA,IAAgB,EAAC;AAAA,IAChC,SAAA,EAAW,QAAQ,SAAA,IAAa,IAAA;AAAA,IAChC,WAAA,EAAa;AAAA,GACf;AACA,EAAA,MAAM,SAAA,uBAAgB,GAAA,EAAwC;AAC9D,EAAA,IAAI,UAAA,GAAqC,IAAA;AAEzC,EAAA,SAAS,KAAK,IAAA,EAA+B;AAC3C,IAAA,KAAA,GAAQ,IAAA;AACR,IAAA,KAAA,MAAW,QAAA,IAAY,SAAA,EAAW,QAAA,CAAS,KAAK,CAAA;AAAA,EAClD;AAGA,EAAA,SAAS,UAAU,KAAA,EAAmC;AACpD,IAAA,MAAM,SAAA,GAAY,KAAA,CAAM,KAAA,CAAM,MAAA,GAAS,CAAA;AACvC,IAAA,IAAI,YAAY,CAAA,EAAG;AACnB,IAAA,MAAM,KAAA,GAAQ,CAAC,GAAG,KAAA,CAAM,KAAK,CAAA;AAC7B,IAAA,KAAA,CAAM,SAAS,CAAA,GAAI,KAAA,CAAM,KAAA,CAAM,SAAS,CAAC,CAAA;AACzC,IAAA,IAAA,CAAK,EAAE,GAAG,KAAA,EAAO,KAAA,EAAO,CAAA;AAAA,EAC1B;AAEA,EAAA,OAAO;AAAA,IACL,UAAU,MAAM,KAAA;AAAA,IAEhB,UAAU,QAAA,EAAU;AAClB,MAAA,SAAA,CAAU,IAAI,QAAQ,CAAA;AACtB,MAAA,OAAO,MAAM,SAAA,CAAU,MAAA,CAAO,QAAQ,CAAA;AAAA,IACxC,CAAA;AAAA,IAEA,KAAA,GAAQ;AACN,MAAA,UAAA,EAAY,KAAA,EAAM;AAAA,IACpB,CAAA;AAAA,IAEA,KAAA,GAAQ;AACN,MAAA,UAAA,EAAY,KAAA,EAAM;AAClB,MAAA,IAAA,CAAK,EAAE,OAAO,EAAC,EAAG,WAAW,IAAA,EAAM,WAAA,EAAa,OAAO,CAAA;AAAA,IACzD,CAAA;AAAA,IAEA,MAAM,IAAA,CAAK,OAAA,EAAS,WAAA,GAAc,EAAC,EAAG;AACpC,MAAA,UAAA,EAAY,KAAA,EAAM;AAClB,MAAA,UAAA,GAAa,IAAI,eAAA,EAAgB;AACjC,MAAA,MAAM,SAAS,UAAA,CAAW,MAAA;AAE1B,MAAA,IAAA,CAAK;AAAA,QACH,GAAG,KAAA;AAAA,QACH,KAAA,EAAO,CAAC,GAAG,KAAA,CAAM,OAAO,UAAA,CAAW,OAAA,EAAS,WAAA,CAAY,MAAM,CAAC,CAAA;AAAA,QAC/D,WAAA,EAAa;AAAA,OACd,CAAA;AAED,MAAA,IAAI;AACF,QAAA,MAAM,MAAA,GAAS,MAAA,CAAO,WAAA,CAAY,OAAA,EAAS;AAAA,UACzC,WAAW,KAAA,CAAM,SAAA;AAAA,UACjB,GAAI,YAAY,MAAA,GAAS,EAAE,QAAQ,WAAA,CAAY,MAAA,KAAW,EAAC;AAAA,UAC3D,GAAI,YAAY,SAAA,GAAY,EAAE,WAAW,WAAA,CAAY,SAAA,KAAc,EAAC;AAAA,UACpE;AAAA,SACD,CAAA;AAED,QAAA,WAAA,MAAiB,SAAS,MAAA,EAAQ;AAChC,UAAA,IAAI,OAAO,OAAA,EAAS;AAEpB,UAAA,QAAQ,MAAM,IAAA;AAAM,YAClB,KAAK,SAAA;AACH,cAAA,IAAA,CAAK,EAAE,GAAG,KAAA,EAAO,SAAA,EAAW,KAAA,CAAM,YAAY,CAAA;AAC9C,cAAA,OAAA,CAAQ,WAAA,GAAc,MAAM,UAAU,CAAA;AACtC,cAAA;AAAA,YACF,KAAK,MAAA;AACH,cAAA,SAAA,CAAU,CAAC,UAAU,EAAE,GAAG,MAAM,IAAA,EAAM,KAAA,CAAM,MAAK,CAAE,CAAA;AACnD,cAAA;AAAA,YACF,KAAK,MAAA;AACH,cAAA,SAAA,CAAU,CAAC,IAAA,MAAU,EAAE,GAAG,IAAA,EAAM,KAAA,EAAO,SAAA,CAAU,IAAA,CAAK,KAAA,EAAO,KAAA,CAAM,IAAI,CAAA,EAAE,CAAE,CAAA;AAC3E,cAAA;AAAA,YACF,KAAK,OAAA;AACH,cAAA,SAAA,CAAU,CAAC,IAAA,MAAU;AAAA,gBACnB,GAAG,IAAA;AAAA,gBACH,gBAAA,EAAkB,IAAA,CAAK,gBAAA,GAAmB,KAAA,CAAM;AAAA,eAClD,CAAE,CAAA;AACF,cAAA;AAAA,YACF,KAAK,UAAA;AACH,cAAA,SAAA,CAAU,CAAC,IAAA,KAAS;AAClB,gBAAA,MAAM,SAAA,GAAY,IAAI,GAAA,CAAI,IAAA,CAAK,SAAS,CAAA;AACxC,gBAAA,SAAA,CAAU,GAAA,CAAI,KAAA,CAAM,KAAA,EAAO,KAAA,CAAM,MAAM,CAAA;AACvC,gBAAA,OAAO,EAAE,GAAG,IAAA,EAAM,SAAA,EAAU;AAAA,cAC9B,CAAC,CAAA;AACD,cAAA;AAAA,YACF,KAAK,SAAA;AACH,cAAA,SAAA,CAAU,CAAC,UAAU,EAAE,GAAG,MAAM,OAAA,EAAS,KAAA,CAAM,OAAM,CAAE,CAAA;AACvD,cAAA;AAAA,YACF,KAAK,kBAAA;AACH,cAAA,SAAA,CAAU,CAAC,IAAA,MAAU;AAAA,gBACnB,GAAG,IAAA;AAAA,gBACH,eAAA,EAAiB;AAAA,kBACf,GAAG,IAAA,CAAK,eAAA;AAAA,kBACR,CAAC,KAAA,CAAM,WAAW,GAAG,KAAA,CAAM;AAAA;AAC7B,eACF,CAAE,CAAA;AACF,cAAA;AAAA,YACF,KAAK,UAAA;AACH,cAAA,SAAA,CAAU,CAAC,IAAA,MAAU;AAAA,gBACnB,GAAG,IAAA;AAAA,gBACH,QAAA,EAAU,EAAE,QAAA,EAAU,KAAA,CAAM,YAAY,IAAA,EAAM,OAAA,EAAS,MAAM,OAAA;AAAQ,eACvE,CAAE,CAAA;AACF,cAAA;AAAA,YACF,KAAK,MAAA;AACH,cAAA,SAAA,CAAU,CAAC,IAAA,MAAU;AAAA,gBACnB,GAAG,IAAA;AAAA,gBACH,WAAA,EAAa,KAAA;AAAA,gBACb,GAAI,MAAM,UAAA,GAAa,EAAE,WAAW,KAAA,CAAM,UAAA,KAAe;AAAC,eAC5D,CAAE,CAAA;AACF,cAAA,IAAA,CAAK,EAAE,GAAG,KAAA,EAAO,WAAA,EAAa,OAAO,CAAA;AACrC,cAAA;AAAA,YACF,KAAK,OAAA;AACH,cAAA,YAAA,CAAa,SAAA,EAAW;AAAA,gBACtB,MAAM,KAAA,CAAM,IAAA;AAAA,gBACZ,SAAS,KAAA,CAAM,OAAA;AAAA,gBACf,aAAA,EAAe,MAAM,cAAA,IAAkB;AAAA,eACxC,CAAA;AACD,cAAA;AAAA;AACJ,QACF;AAAA,MACF,SAAS,GAAA,EAAK;AACZ,QAAA,IAAK,GAAA,EAAe,SAAS,YAAA,EAAc;AACzC,UAAA,SAAA,CAAU,CAAC,IAAA,MAAU,EAAE,GAAG,IAAA,EAAM,WAAA,EAAa,OAAM,CAAE,CAAA;AAAA,QACvD,CAAA,MAAA,IAAW,eAAe,aAAA,EAAe;AACvC,UAAA,YAAA,CAAa,SAAA,EAAW;AAAA,YACtB,MAAM,GAAA,CAAI,IAAA;AAAA,YACV,SAAS,GAAA,CAAI,OAAA;AAAA,YACb,aAAA,EAAe,IAAA;AAAA,YACf,GAAI,IAAI,cAAA,GAAiB,EAAE,gBAAgB,GAAA,CAAI,cAAA,KAAmB,EAAC;AAAA,YACnE,GAAI,IAAI,eAAA,GAAkB,EAAE,iBAAiB,GAAA,CAAI,eAAA,KAAoB;AAAC,WACvE,CAAA;AAAA,QACH,CAAA,MAAO;AACL,UAAA,YAAA,CAAa,SAAA,EAAW;AAAA,YACtB,IAAA,EAAM,eAAA;AAAA,YACN,OAAA,EAAS,GAAA,YAAe,KAAA,GAAQ,GAAA,CAAI,OAAA,GAAU,4BAAA;AAAA,YAC9C,aAAA,EAAe;AAAA,WAChB,CAAA;AAAA,QACH;AAAA,MACF,CAAA,SAAE;AAIA,QAAA,MAAM,IAAA,GAAO,KAAA,CAAM,KAAA,CAAM,EAAA,CAAG,EAAE,CAAA;AAC9B,QAAA,IAAI,IAAA,EAAM,WAAA,EAAa,SAAA,CAAU,CAAC,IAAA,MAAU,EAAE,GAAG,IAAA,EAAM,WAAA,EAAa,KAAA,EAAM,CAAE,CAAA;AAC5E,QAAA,IAAI,KAAA,CAAM,aAAa,IAAA,CAAK,EAAE,GAAG,KAAA,EAAO,WAAA,EAAa,OAAO,CAAA;AAC5D,QAAA,UAAA,GAAa,IAAA;AAAA,MACf;AAAA,IACF,CAAA;AAAA,IAEA,MAAM,WAAA,CAAY,MAAA,EAAQ,QAAA,EAAU;AAClC,MAAA,MAAM,IAAA,GAAO,MAAM,KAAA,CAAM,IAAA,CAAK,CAAC,CAAA,KAAM,CAAA,CAAE,OAAO,MAAM,CAAA;AACpD,MAAA,IAAI,CAAC,MAAM,SAAA,EAAW;AACpB,QAAA,MAAM,IAAI,aAAA;AAAA,UACR,sBAAA;AAAA,UACA;AAAA,SACF;AAAA,MACF;AACA,MAAA,MAAM,MAAA,CAAO,cAAA,CAAe,IAAA,CAAK,SAAA,EAAW,QAAQ,CAAA;AACpD,MAAA,IAAA,CAAK;AAAA,QACH,GAAG,KAAA;AAAA,QACH,KAAA,EAAO,KAAA,CAAM,KAAA,CAAM,GAAA,CAAI,CAAC,CAAA,KAAO,CAAA,CAAE,EAAA,KAAO,MAAA,GAAS,EAAE,GAAG,CAAA,EAAG,QAAA,KAAa,CAAE;AAAA,OACzE,CAAA;AAAA,IACH;AAAA,GACF;AACF;AAMA,SAAS,SAAA,CAAU,OAAoB,IAAA,EAA8B;AACnE,EAAA,IAAI,KAAK,MAAA,KAAW,WAAA,SAAoB,CAAC,GAAG,OAAO,IAAI,CAAA;AACvD,EAAA,MAAM,YAAA,GAAe,KAAA,CAAM,SAAA,CAAU,CAAC,CAAA,KAAM,CAAA,CAAE,IAAA,KAAS,IAAA,CAAK,IAAA,IAAQ,CAAA,CAAE,MAAA,KAAW,SAAS,CAAA;AAC1F,EAAA,IAAI,iBAAiB,EAAA,EAAI,OAAO,CAAC,GAAG,OAAO,IAAI,CAAA;AAC/C,EAAA,MAAM,IAAA,GAAO,CAAC,GAAG,KAAK,CAAA;AACtB,EAAA,IAAA,CAAK,YAAY,CAAA,GAAI,IAAA;AACrB,EAAA,OAAO,IAAA;AACT;AAUA,SAAS,YAAA,CACP,WACA,OAAA,EAOM;AACN,EAAA,IAAI,OAAA,CAAQ,SAAS,qBAAA,EAAuB;AAC1C,IAAA,SAAA,CAAU,CAAC,IAAA,MAAU;AAAA,MACnB,GAAG,IAAA;AAAA,MACH,WAAA,EAAa,KAAA;AAAA,MACb,gBAAA,EAAkB,EAAA;AAAA,MAClB,SAAA,sBAAe,GAAA,EAAI;AAAA,MACnB,SAAS,EAAC;AAAA,MACV,iBAAiB,EAAC;AAAA,MAClB,OAAO,EAAC;AAAA,MACR,IAAA,EAAM,IAAA;AAAA,MACN,QAAA,EAAU,MAAA;AAAA,MACV,SAAS,EAAE,IAAA,EAAM,QAAQ,aAAA,EAAe,OAAA,EAAS,QAAQ,OAAA;AAAQ,KACnE,CAAE,CAAA;AACF,IAAA;AAAA,EACF;AAEA,EAAA,SAAA,CAAU,CAAC,IAAA,MAAU;AAAA,IACnB,GAAG,IAAA;AAAA,IACH,WAAA,EAAa,KAAA;AAAA,IACb,KAAA,EAAO;AAAA,MACL,MAAM,OAAA,CAAQ,IAAA;AAAA,MACd,SAAS,OAAA,CAAQ,OAAA;AAAA,MACjB,GAAI,QAAQ,cAAA,GAAiB,EAAE,gBAAgB,OAAA,CAAQ,cAAA,KAAmB,EAAC;AAAA,MAC3E,GAAI,QAAQ,eAAA,GAAkB,EAAE,iBAAiB,OAAA,CAAQ,eAAA,KAAoB;AAAC;AAChF,GACF,CAAE,CAAA;AACJ;;;ACpVA,IAAM,eAAA,GAAkB,eAAA;AAExB,SAAS,YAAY,MAAA,EAA8B;AACjD,EAAA,MAAM,QAAQ,MAAA,CAAO,UAAA,GAAa,gBAAgB,IAAA,CAAK,MAAA,CAAO,UAAU,CAAA,GAAI,IAAA;AAC5E,EAAA,OAAO,KAAA,GAAQ,OAAO,QAAA,CAAS,KAAA,CAAM,CAAC,CAAA,EAAG,EAAE,IAAI,MAAA,CAAO,iBAAA;AACxD;AAEA,SAAS,iBAAiB,MAAA,EAAsC;AAC9D,EAAA,OAAO;AAAA,IACL,SAAA,EAAW,MAAA,CAAO,UAAA,IAAc,MAAA,CAAO,GAAA;AAAA,IACvC,OAAO,MAAA,CAAO,KAAA;AAAA,IACd,KAAK,MAAA,CAAO,GAAA;AAAA,IACZ,cAAc,MAAA,CAAO,YAAA;AAAA,IACrB,GAAI,OAAO,OAAA,GAAU,EAAE,SAAS,MAAA,CAAO,OAAA,KAAY;AAAC,GACtD;AACF;AAWO,SAAS,eAAA,CAAgB,OAAe,IAAA,EAAkE;AAC/G,EAAA,MAAM,KAAA,GAAQ,IAAA,CAAK,SAAA,CAAU,GAAA,CAAI,KAAK,CAAA;AACtC,EAAA,IAAI,OAAO,OAAO,KAAA;AAClB,EAAA,IAAI,QAAQ,CAAA,IAAK,IAAA,CAAK,OAAA,CAAQ,MAAA,KAAW,GAAG,OAAO,IAAA;AAEnD,EAAA,MAAM,OAAA,GAAU,CAAC,GAAG,IAAA,CAAK,OAAO,CAAA,CAAE,IAAA,CAAK,CAAC,CAAA,EAAG,MAAM,WAAA,CAAY,CAAC,CAAA,GAAI,WAAA,CAAY,CAAC,CAAC,CAAA;AAChF,EAAA,MAAM,QAAA,GAAW,OAAA,CAAQ,KAAA,GAAQ,CAAC,CAAA;AAClC,EAAA,OAAO,QAAA,GAAW,gBAAA,CAAiB,QAAQ,CAAA,GAAI,IAAA;AACjD;AAOO,SAAS,YAAY,MAAA,EAAiC;AAC3D,EAAA,OAAO,OAAO,OAAO,GAAA,KAAQ,QAAA,IAAY,OAAO,GAAA,CAAI,IAAA,GAAO,MAAA,GAAS,CAAA;AACtE;AAOA,IAAM,gBAAA,GAAmB,YAAA;AAOlB,SAAS,aAAA,CACd,SACA,IAAA,EACiB;AACjB,EAAA,MAAM,WAA4B,EAAC;AACnC,EAAA,IAAI,SAAA,GAAY,CAAA;AAEhB,EAAA,KAAA,MAAW,KAAA,IAAS,OAAA,CAAQ,QAAA,CAAS,gBAAgB,CAAA,EAAG;AACtD,IAAA,MAAM,EAAA,GAAK,MAAM,KAAA,IAAS,CAAA;AAC1B,IAAA,IAAI,EAAA,GAAK,SAAA,EAAW,QAAA,CAAS,IAAA,CAAK,EAAE,IAAA,EAAM,MAAA,EAAQ,IAAA,EAAM,OAAA,CAAQ,KAAA,CAAM,SAAA,EAAW,EAAE,GAAG,CAAA;AACtF,IAAA,MAAM,QAAQ,MAAA,CAAO,QAAA,CAAS,KAAA,CAAM,CAAC,GAAG,EAAE,CAAA;AAC1C,IAAA,QAAA,CAAS,IAAA,CAAK,EAAE,IAAA,EAAM,UAAA,EAAY,KAAA,EAAO,QAAQ,eAAA,CAAgB,KAAA,EAAO,IAAI,CAAA,EAAG,CAAA;AAC/E,IAAA,SAAA,GAAY,EAAA,GAAK,KAAA,CAAM,CAAC,CAAA,CAAE,MAAA;AAAA,EAC5B;AACA,EAAA,IAAI,SAAA,GAAY,QAAQ,MAAA,EAAQ;AAC9B,IAAA,QAAA,CAAS,IAAA,CAAK,EAAE,IAAA,EAAM,MAAA,EAAQ,MAAM,OAAA,CAAQ,KAAA,CAAM,SAAS,CAAA,EAAG,CAAA;AAAA,EAChE;AACA,EAAA,OAAO,QAAA;AACT","file":"chunk-OSMAH2QV.js","sourcesContent":["/**\n * Every failure the SDK surfaces, with the API's own error code attached.\n *\n * Callers switch on `code` rather than parsing messages: the server's copy is\n * user-facing prose that may change, while the code is part of the contract.\n */\nexport class LainternError extends Error {\n readonly code: string\n readonly status: number | null\n /** Set on RATE_LIMIT_EXCEEDED. */\n readonly rateLimitScope?: \"user\" | \"agent\"\n readonly rateLimitWindow?: \"minute\" | \"hour\" | \"day\"\n /** Set on MCP_UNAVAILABLE: which dependency failed, and how. */\n readonly reason?: string\n\n constructor(\n code: string,\n message: string,\n options: {\n status?: number | null\n rateLimitScope?: \"user\" | \"agent\"\n rateLimitWindow?: \"minute\" | \"hour\" | \"day\"\n reason?: string\n cause?: unknown\n } = {},\n ) {\n super(message, options.cause !== undefined ? { cause: options.cause } : undefined)\n this.name = \"LainternError\"\n this.code = code\n this.status = options.status ?? null\n if (options.rateLimitScope) this.rateLimitScope = options.rateLimitScope\n if (options.rateLimitWindow) this.rateLimitWindow = options.rateLimitWindow\n if (options.reason) this.reason = options.reason\n }\n\n /**\n * True when retrying later could plausibly succeed: a rate limit, or a\n * dependency the server could not reach. An expired session is NOT retryable\n * — the host has to mint a new token first.\n */\n get isRetryable(): boolean {\n return this.code === \"RATE_LIMIT_EXCEEDED\" || this.reason === \"transport\"\n }\n}\n\nfunction pick<T extends string>(value: unknown, allowed: readonly T[]): T | undefined {\n return typeof value === \"string\" && (allowed as readonly string[]).includes(value)\n ? (value as T)\n : undefined\n}\n\n/** Turn a non-2xx API response into a `LainternError`. Never throws itself. */\nexport async function errorFromResponse(response: Response): Promise<LainternError> {\n let code = `HTTP_${response.status}`\n let message = \"An unexpected error occurred.\"\n let rateLimitScope: \"user\" | \"agent\" | undefined\n let rateLimitWindow: \"minute\" | \"hour\" | \"day\" | undefined\n let reason: string | undefined\n\n try {\n const body = (await response.json()) as {\n error?: {\n code?: string\n message?: string\n scope?: unknown\n window?: unknown\n reason?: unknown\n }\n message?: string\n }\n if (body?.error?.code) {\n code = body.error.code\n message = body.error.message ?? message\n rateLimitScope = pick(body.error.scope, [\"user\", \"agent\"] as const)\n rateLimitWindow = pick(body.error.window, [\"minute\", \"hour\", \"day\"] as const)\n if (typeof body.error.reason === \"string\") reason = body.error.reason\n } else if (typeof body?.message === \"string\") {\n message = body.message\n }\n } catch {\n // Not JSON — keep the HTTP-derived defaults.\n }\n\n return new LainternError(code, message, {\n status: response.status,\n rateLimitScope,\n rateLimitWindow,\n reason,\n })\n}\n","import type {\n CitationSource,\n ExternalSourceType,\n SearchResult,\n StepEvent,\n} from \"@laintern-headless/types\"\nimport type { LainternClient } from \"./client.js\"\nimport { LainternError } from \"./errors.js\"\n\n/** Soft advisory alongside an answer — the agent flagged the topic, but answered. */\nexport interface TurnAdvisory {\n redirect: string | null\n message: string\n}\n\n/** A guardrail refused the turn. The agent produced no answer. */\nexport interface TurnBlock {\n /** Which guardrail fired, when the server names one. */\n slug: string | null\n /** Copy meant for the end user, written by the guardrail's configuration. */\n message: string\n}\n\nexport interface TurnError {\n code: string\n message: string\n rateLimitScope?: \"user\" | \"agent\"\n rateLimitWindow?: \"minute\" | \"hour\" | \"day\"\n}\n\nexport interface Turn {\n id: string\n userMessage: string\n userImages?: string[]\n assistantContent: string\n /** Citation number (the `[1]` in the text) → the source it points at. */\n citations: Map<number, CitationSource>\n /** Everything the agent cited or consulted, in the order the server ranked it. */\n sources: SearchResult[]\n /** Sidebar suggestions per content type — not necessarily cited. */\n externalSources: Partial<Record<ExternalSourceType, SearchResult[]>>\n mode: string | null\n steps: StepEvent[]\n isStreaming: boolean\n error?: TurnError\n blocked?: TurnBlock\n advisory?: TurnAdvisory\n messageId?: string\n feedback?: \"positive\" | \"negative\" | null\n}\n\nexport interface ConversationState {\n turns: Turn[]\n sessionId: string | null\n isStreaming: boolean\n}\n\nexport interface SendOptions {\n images?: string[]\n forceTool?: string\n}\n\nexport interface Conversation {\n getState(): ConversationState\n /** Subscribe to state changes. Returns an unsubscribe function. */\n subscribe(listener: (state: ConversationState) => void): () => void\n /** Send a message and stream the answer into state. Never throws. */\n send(message: string, options?: SendOptions): Promise<void>\n /** Stop the current stream. The partial answer stays in state. */\n abort(): void\n /** Drop all turns and start a new server-side session on the next send. */\n reset(): void\n setFeedback(turnId: string, feedback: \"positive\" | \"negative\"): Promise<void>\n}\n\nexport interface ConversationOptions {\n /** Resume an existing server-side session. */\n sessionId?: string | null\n /** Restore turns from your own storage. */\n initialTurns?: Turn[]\n /** Called whenever the session id changes, so you can persist it. */\n onSessionId?: (sessionId: string) => void\n}\n\nfunction newId(): string {\n const c = globalThis.crypto\n if (c && typeof c.randomUUID === \"function\") return c.randomUUID()\n return `turn-${Date.now()}-${Math.random().toString(36).slice(2, 10)}`\n}\n\nfunction createTurn(message: string, images?: string[]): Turn {\n return {\n id: newId(),\n userMessage: message,\n ...(images?.length ? { userImages: images } : {}),\n assistantContent: \"\",\n citations: new Map(),\n sources: [],\n externalSources: {},\n mode: null,\n steps: [],\n isStreaming: true,\n }\n}\n\n/**\n * Stateful conversation on top of {@link LainternClient}.\n *\n * Holds the turn list, applies each stream event to the turn in flight, and\n * notifies subscribers. Framework-agnostic on purpose: the React binding in\n * `@laintern/chat-sdk/react` is a thin `useSyncExternalStore` wrapper, and any\n * other UI layer can subscribe the same way.\n *\n * `send()` never throws. A failed turn is part of the conversation — it lands\n * on `turn.error` (or `turn.blocked` for a guardrail) so the UI can render it\n * in place, which is what you want in a chat transcript.\n */\nexport function createConversation(\n client: LainternClient,\n options: ConversationOptions = {},\n): Conversation {\n let state: ConversationState = {\n turns: options.initialTurns ?? [],\n sessionId: options.sessionId ?? null,\n isStreaming: false,\n }\n const listeners = new Set<(state: ConversationState) => void>()\n let controller: AbortController | null = null\n\n function emit(next: ConversationState): void {\n state = next\n for (const listener of listeners) listener(state)\n }\n\n /** Apply a change to the turn currently streaming (always the last one). */\n function patchLast(patch: (turn: Turn) => Turn): void {\n const lastIndex = state.turns.length - 1\n if (lastIndex < 0) return\n const turns = [...state.turns]\n turns[lastIndex] = patch(turns[lastIndex])\n emit({ ...state, turns })\n }\n\n return {\n getState: () => state,\n\n subscribe(listener) {\n listeners.add(listener)\n return () => listeners.delete(listener)\n },\n\n abort() {\n controller?.abort()\n },\n\n reset() {\n controller?.abort()\n emit({ turns: [], sessionId: null, isStreaming: false })\n },\n\n async send(message, sendOptions = {}) {\n controller?.abort()\n controller = new AbortController()\n const signal = controller.signal\n\n emit({\n ...state,\n turns: [...state.turns, createTurn(message, sendOptions.images)],\n isStreaming: true,\n })\n\n try {\n const stream = client.sendMessage(message, {\n sessionId: state.sessionId,\n ...(sendOptions.images ? { images: sendOptions.images } : {}),\n ...(sendOptions.forceTool ? { forceTool: sendOptions.forceTool } : {}),\n signal,\n })\n\n for await (const event of stream) {\n if (signal.aborted) break\n\n switch (event.type) {\n case \"session\":\n emit({ ...state, sessionId: event.session_id })\n options.onSessionId?.(event.session_id)\n break\n case \"mode\":\n patchLast((turn) => ({ ...turn, mode: event.mode }))\n break\n case \"step\":\n patchLast((turn) => ({ ...turn, steps: mergeStep(turn.steps, event.step) }))\n break\n case \"delta\":\n patchLast((turn) => ({\n ...turn,\n assistantContent: turn.assistantContent + event.content,\n }))\n break\n case \"citation\":\n patchLast((turn) => {\n const citations = new Map(turn.citations)\n citations.set(event.index, event.source)\n return { ...turn, citations }\n })\n break\n case \"sources\":\n patchLast((turn) => ({ ...turn, sources: event.items }))\n break\n case \"external_sources\":\n patchLast((turn) => ({\n ...turn,\n externalSources: {\n ...turn.externalSources,\n [event.source_type]: event.items,\n },\n }))\n break\n case \"advisory\":\n patchLast((turn) => ({\n ...turn,\n advisory: { redirect: event.redirect ?? null, message: event.message },\n }))\n break\n case \"done\":\n patchLast((turn) => ({\n ...turn,\n isStreaming: false,\n ...(event.message_id ? { messageId: event.message_id } : {}),\n }))\n emit({ ...state, isStreaming: false })\n break\n case \"error\":\n applyFailure(patchLast, {\n code: event.code,\n message: event.message,\n guardrailSlug: event.guardrail_slug ?? null,\n })\n break\n }\n }\n } catch (err) {\n if ((err as Error)?.name === \"AbortError\") {\n patchLast((turn) => ({ ...turn, isStreaming: false }))\n } else if (err instanceof LainternError) {\n applyFailure(patchLast, {\n code: err.code,\n message: err.message,\n guardrailSlug: null,\n ...(err.rateLimitScope ? { rateLimitScope: err.rateLimitScope } : {}),\n ...(err.rateLimitWindow ? { rateLimitWindow: err.rateLimitWindow } : {}),\n })\n } else {\n applyFailure(patchLast, {\n code: \"NETWORK_ERROR\",\n message: err instanceof Error ? err.message : \"An unknown error occurred.\",\n guardrailSlug: null,\n })\n }\n } finally {\n // A turn always stops streaming, however it ended — including an abort\n // that broke the loop without throwing. Leaving the flag set strands a\n // typing indicator on a turn that will never produce another token.\n const last = state.turns.at(-1)\n if (last?.isStreaming) patchLast((turn) => ({ ...turn, isStreaming: false }))\n if (state.isStreaming) emit({ ...state, isStreaming: false })\n controller = null\n }\n },\n\n async setFeedback(turnId, feedback) {\n const turn = state.turns.find((t) => t.id === turnId)\n if (!turn?.messageId) {\n throw new LainternError(\n \"FEEDBACK_UNAVAILABLE\",\n \"This turn has no message id yet — feedback is only possible once a turn completes.\",\n )\n }\n await client.submitFeedback(turn.messageId, feedback)\n emit({\n ...state,\n turns: state.turns.map((t) => (t.id === turnId ? { ...t, feedback } : t)),\n })\n },\n }\n}\n\n/**\n * A completed step replaces its own \"started\" entry rather than appending, so\n * the UI shows one row per tool call that fills in, not two.\n */\nfunction mergeStep(steps: StepEvent[], step: StepEvent): StepEvent[] {\n if (step.status !== \"completed\") return [...steps, step]\n const startedIndex = steps.findIndex((s) => s.tool === step.tool && s.status === \"started\")\n if (startedIndex === -1) return [...steps, step]\n const next = [...steps]\n next[startedIndex] = step\n return next\n}\n\n/**\n * A failed or blocked turn.\n *\n * A guardrail block discards whatever streamed before it fired: the partial\n * answer is exactly the content the guardrail decided the user should not see,\n * and a hard block always wins over a soft advisory that arrived a moment\n * earlier.\n */\nfunction applyFailure(\n patchLast: (patch: (turn: Turn) => Turn) => void,\n failure: {\n code: string\n message: string\n guardrailSlug: string | null\n rateLimitScope?: \"user\" | \"agent\"\n rateLimitWindow?: \"minute\" | \"hour\" | \"day\"\n },\n): void {\n if (failure.code === \"GUARDRAIL_TRIGGERED\") {\n patchLast((turn) => ({\n ...turn,\n isStreaming: false,\n assistantContent: \"\",\n citations: new Map(),\n sources: [],\n externalSources: {},\n steps: [],\n mode: null,\n advisory: undefined,\n blocked: { slug: failure.guardrailSlug, message: failure.message },\n }))\n return\n }\n\n patchLast((turn) => ({\n ...turn,\n isStreaming: false,\n error: {\n code: failure.code,\n message: failure.message,\n ...(failure.rateLimitScope ? { rateLimitScope: failure.rateLimitScope } : {}),\n ...(failure.rateLimitWindow ? { rateLimitWindow: failure.rateLimitWindow } : {}),\n },\n }))\n}\n","import type { CitationSource, SearchResult } from \"@laintern-headless/types\"\nimport type { Turn } from \"./conversation.js\"\n\n/** Source ids the server hands out are `source0`, `source1`, … in call order. */\nconst SOURCE_INDEX_RE = /^source(\\d+)$/\n\nfunction sourceIndex(source: SearchResult): number {\n const match = source.content_id ? SOURCE_INDEX_RE.exec(source.content_id) : null\n return match ? Number.parseInt(match[1], 10) : Number.POSITIVE_INFINITY\n}\n\nfunction fromSearchResult(source: SearchResult): CitationSource {\n return {\n source_id: source.content_id || source.url,\n title: source.title,\n url: source.url,\n content_type: source.content_type,\n ...(source.section ? { section: source.section } : {}),\n }\n}\n\n/**\n * Resolve a `[n]` marker in the answer text to the source it points at.\n *\n * The answer streams token by token, so a marker can render before its\n * `citation` event arrives. The fallback covers that gap by position: sources\n * are numbered in the order the agent collected them, so the n-th source is\n * the one the n-th marker means. Returns null when neither is available yet —\n * render the bare `[n]` and let it resolve on the next frame.\n */\nexport function resolveCitation(index: number, turn: Pick<Turn, \"citations\" | \"sources\">): CitationSource | null {\n const exact = turn.citations.get(index)\n if (exact) return exact\n if (index < 1 || turn.sources.length === 0) return null\n\n const ordered = [...turn.sources].sort((a, b) => sourceIndex(a) - sourceIndex(b))\n const fallback = ordered[index - 1]\n return fallback ? fromSearchResult(fallback) : null\n}\n\n/**\n * True when a citation has nowhere to link to — a knowledge-base passage\n * lives in the client's own document store, not on the public web. Render\n * those as text (title, plus `section` when present), never as a dead link.\n */\nexport function isNavigable(source: CitationSource): boolean {\n return typeof source.url === \"string\" && source.url.trim().length > 0\n}\n\n/** Splits the answer into text and citation markers, in order. */\nexport type AnswerSegment =\n | { type: \"text\"; text: string }\n | { type: \"citation\"; index: number; source: CitationSource | null }\n\nconst CITATION_PATTERN = /\\[(\\d+)\\]/g\n\n/**\n * Walk the answer text and hand back its segments, with each `[n]` already\n * resolved. Lets a UI render citations as components without writing its own\n * parser — and without turning the markdown into HTML first.\n */\nexport function segmentAnswer(\n content: string,\n turn: Pick<Turn, \"citations\" | \"sources\">,\n): AnswerSegment[] {\n const segments: AnswerSegment[] = []\n let lastIndex = 0\n\n for (const match of content.matchAll(CITATION_PATTERN)) {\n const at = match.index ?? 0\n if (at > lastIndex) segments.push({ type: \"text\", text: content.slice(lastIndex, at) })\n const index = Number.parseInt(match[1], 10)\n segments.push({ type: \"citation\", index, source: resolveCitation(index, turn) })\n lastIndex = at + match[0].length\n }\n if (lastIndex < content.length) {\n segments.push({ type: \"text\", text: content.slice(lastIndex) })\n }\n return segments\n}\n"]}
@@ -281,4 +281,36 @@ interface ConversationOptions {
281
281
  */
282
282
  declare function createConversation(client: LainternClient, options?: ConversationOptions): Conversation;
283
283
 
284
- export { type AgentConfig as A, type ChatStreamEvent as C, DEFAULT_REFRESH_BUFFER_MS as D, type ExternalSourceType as E, type LainternClient as L, type SearchResult as S, type TokenManager as T, type ChatContentType as a, type CitationSource as b, type ContentType as c, type Conversation as d, type ConversationOptions as e, type ConversationState as f, type LainternClientOptions as g, type SendMessageOptions as h, type SendOptions as i, type StepEvent as j, type TokenManagerOptions as k, type TokenResponse as l, type TokenStorage as m, type Turn as n, type TurnAdvisory as o, type TurnBlock as p, type TurnError as q, createConversation as r, createLainternClient as s, createTokenManager as t, decodeJwtExpiryMs as u };
284
+ /**
285
+ * Resolve a `[n]` marker in the answer text to the source it points at.
286
+ *
287
+ * The answer streams token by token, so a marker can render before its
288
+ * `citation` event arrives. The fallback covers that gap by position: sources
289
+ * are numbered in the order the agent collected them, so the n-th source is
290
+ * the one the n-th marker means. Returns null when neither is available yet —
291
+ * render the bare `[n]` and let it resolve on the next frame.
292
+ */
293
+ declare function resolveCitation(index: number, turn: Pick<Turn, "citations" | "sources">): CitationSource | null;
294
+ /**
295
+ * True when a citation has nowhere to link to — a knowledge-base passage
296
+ * lives in the client's own document store, not on the public web. Render
297
+ * those as text (title, plus `section` when present), never as a dead link.
298
+ */
299
+ declare function isNavigable(source: CitationSource): boolean;
300
+ /** Splits the answer into text and citation markers, in order. */
301
+ type AnswerSegment = {
302
+ type: "text";
303
+ text: string;
304
+ } | {
305
+ type: "citation";
306
+ index: number;
307
+ source: CitationSource | null;
308
+ };
309
+ /**
310
+ * Walk the answer text and hand back its segments, with each `[n]` already
311
+ * resolved. Lets a UI render citations as components without writing its own
312
+ * parser — and without turning the markdown into HTML first.
313
+ */
314
+ declare function segmentAnswer(content: string, turn: Pick<Turn, "citations" | "sources">): AnswerSegment[];
315
+
316
+ export { type AgentConfig as A, type ChatStreamEvent as C, DEFAULT_REFRESH_BUFFER_MS as D, type ExternalSourceType as E, type LainternClient as L, type SearchResult as S, type TokenManager as T, type AnswerSegment as a, type ChatContentType as b, type CitationSource as c, type ContentType as d, type Conversation as e, type ConversationOptions as f, type ConversationState as g, type LainternClientOptions as h, type SendMessageOptions as i, type SendOptions as j, type StepEvent as k, type TokenManagerOptions as l, type TokenResponse as m, type TokenStorage as n, type Turn as o, type TurnAdvisory as p, type TurnBlock as q, type TurnError as r, createConversation as s, createLainternClient as t, createTokenManager as u, decodeJwtExpiryMs as v, isNavigable as w, resolveCitation as x, segmentAnswer as y };
@@ -281,4 +281,36 @@ interface ConversationOptions {
281
281
  */
282
282
  declare function createConversation(client: LainternClient, options?: ConversationOptions): Conversation;
283
283
 
284
- export { type AgentConfig as A, type ChatStreamEvent as C, DEFAULT_REFRESH_BUFFER_MS as D, type ExternalSourceType as E, type LainternClient as L, type SearchResult as S, type TokenManager as T, type ChatContentType as a, type CitationSource as b, type ContentType as c, type Conversation as d, type ConversationOptions as e, type ConversationState as f, type LainternClientOptions as g, type SendMessageOptions as h, type SendOptions as i, type StepEvent as j, type TokenManagerOptions as k, type TokenResponse as l, type TokenStorage as m, type Turn as n, type TurnAdvisory as o, type TurnBlock as p, type TurnError as q, createConversation as r, createLainternClient as s, createTokenManager as t, decodeJwtExpiryMs as u };
284
+ /**
285
+ * Resolve a `[n]` marker in the answer text to the source it points at.
286
+ *
287
+ * The answer streams token by token, so a marker can render before its
288
+ * `citation` event arrives. The fallback covers that gap by position: sources
289
+ * are numbered in the order the agent collected them, so the n-th source is
290
+ * the one the n-th marker means. Returns null when neither is available yet —
291
+ * render the bare `[n]` and let it resolve on the next frame.
292
+ */
293
+ declare function resolveCitation(index: number, turn: Pick<Turn, "citations" | "sources">): CitationSource | null;
294
+ /**
295
+ * True when a citation has nowhere to link to — a knowledge-base passage
296
+ * lives in the client's own document store, not on the public web. Render
297
+ * those as text (title, plus `section` when present), never as a dead link.
298
+ */
299
+ declare function isNavigable(source: CitationSource): boolean;
300
+ /** Splits the answer into text and citation markers, in order. */
301
+ type AnswerSegment = {
302
+ type: "text";
303
+ text: string;
304
+ } | {
305
+ type: "citation";
306
+ index: number;
307
+ source: CitationSource | null;
308
+ };
309
+ /**
310
+ * Walk the answer text and hand back its segments, with each `[n]` already
311
+ * resolved. Lets a UI render citations as components without writing its own
312
+ * parser — and without turning the markdown into HTML first.
313
+ */
314
+ declare function segmentAnswer(content: string, turn: Pick<Turn, "citations" | "sources">): AnswerSegment[];
315
+
316
+ export { type AgentConfig as A, type ChatStreamEvent as C, DEFAULT_REFRESH_BUFFER_MS as D, type ExternalSourceType as E, type LainternClient as L, type SearchResult as S, type TokenManager as T, type AnswerSegment as a, type ChatContentType as b, type CitationSource as c, type ContentType as d, type Conversation as e, type ConversationOptions as f, type ConversationState as g, type LainternClientOptions as h, type SendMessageOptions as i, type SendOptions as j, type StepEvent as k, type TokenManagerOptions as l, type TokenResponse as m, type TokenStorage as n, type Turn as o, type TurnAdvisory as p, type TurnBlock as q, type TurnError as r, createConversation as s, createLainternClient as t, createTokenManager as u, decodeJwtExpiryMs as v, isNavigable as w, resolveCitation as x, segmentAnswer as y };
package/dist/index.cjs CHANGED
@@ -527,12 +527,58 @@ function applyFailure(patchLast, failure) {
527
527
  }));
528
528
  }
529
529
 
530
+ // src/citations.ts
531
+ var SOURCE_INDEX_RE = /^source(\d+)$/;
532
+ function sourceIndex(source) {
533
+ const match = source.content_id ? SOURCE_INDEX_RE.exec(source.content_id) : null;
534
+ return match ? Number.parseInt(match[1], 10) : Number.POSITIVE_INFINITY;
535
+ }
536
+ function fromSearchResult(source) {
537
+ return {
538
+ source_id: source.content_id || source.url,
539
+ title: source.title,
540
+ url: source.url,
541
+ content_type: source.content_type,
542
+ ...source.section ? { section: source.section } : {}
543
+ };
544
+ }
545
+ function resolveCitation(index, turn) {
546
+ const exact = turn.citations.get(index);
547
+ if (exact) return exact;
548
+ if (index < 1 || turn.sources.length === 0) return null;
549
+ const ordered = [...turn.sources].sort((a, b) => sourceIndex(a) - sourceIndex(b));
550
+ const fallback = ordered[index - 1];
551
+ return fallback ? fromSearchResult(fallback) : null;
552
+ }
553
+ function isNavigable(source) {
554
+ return typeof source.url === "string" && source.url.trim().length > 0;
555
+ }
556
+ var CITATION_PATTERN = /\[(\d+)\]/g;
557
+ function segmentAnswer(content, turn) {
558
+ const segments = [];
559
+ let lastIndex = 0;
560
+ for (const match of content.matchAll(CITATION_PATTERN)) {
561
+ const at = match.index ?? 0;
562
+ if (at > lastIndex) segments.push({ type: "text", text: content.slice(lastIndex, at) });
563
+ const index = Number.parseInt(match[1], 10);
564
+ segments.push({ type: "citation", index, source: resolveCitation(index, turn) });
565
+ lastIndex = at + match[0].length;
566
+ }
567
+ if (lastIndex < content.length) {
568
+ segments.push({ type: "text", text: content.slice(lastIndex) });
569
+ }
570
+ return segments;
571
+ }
572
+
530
573
  exports.DEFAULT_REFRESH_BUFFER_MS = DEFAULT_REFRESH_BUFFER_MS;
531
574
  exports.LainternError = LainternError;
532
575
  exports.createConversation = createConversation;
533
576
  exports.createLainternClient = createLainternClient;
534
577
  exports.createTokenManager = createTokenManager;
535
578
  exports.decodeJwtExpiryMs = decodeJwtExpiryMs;
579
+ exports.isNavigable = isNavigable;
536
580
  exports.parseSSE = parseSSE;
581
+ exports.resolveCitation = resolveCitation;
582
+ exports.segmentAnswer = segmentAnswer;
537
583
  //# sourceMappingURL=index.cjs.map
538
584
  //# sourceMappingURL=index.cjs.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/errors.ts","../src/sse.ts","../src/tokens.ts","../src/client.ts","../src/conversation.ts"],"names":["parsed"],"mappings":"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","file":"index.cjs","sourcesContent":["/**\n * Every failure the SDK surfaces, with the API's own error code attached.\n *\n * Callers switch on `code` rather than parsing messages: the server's copy is\n * user-facing prose that may change, while the code is part of the contract.\n */\nexport class LainternError extends Error {\n readonly code: string\n readonly status: number | null\n /** Set on RATE_LIMIT_EXCEEDED. */\n readonly rateLimitScope?: \"user\" | \"agent\"\n readonly rateLimitWindow?: \"minute\" | \"hour\" | \"day\"\n /** Set on MCP_UNAVAILABLE: which dependency failed, and how. */\n readonly reason?: string\n\n constructor(\n code: string,\n message: string,\n options: {\n status?: number | null\n rateLimitScope?: \"user\" | \"agent\"\n rateLimitWindow?: \"minute\" | \"hour\" | \"day\"\n reason?: string\n cause?: unknown\n } = {},\n ) {\n super(message, options.cause !== undefined ? { cause: options.cause } : undefined)\n this.name = \"LainternError\"\n this.code = code\n this.status = options.status ?? null\n if (options.rateLimitScope) this.rateLimitScope = options.rateLimitScope\n if (options.rateLimitWindow) this.rateLimitWindow = options.rateLimitWindow\n if (options.reason) this.reason = options.reason\n }\n\n /**\n * True when retrying later could plausibly succeed: a rate limit, or a\n * dependency the server could not reach. An expired session is NOT retryable\n * — the host has to mint a new token first.\n */\n get isRetryable(): boolean {\n return this.code === \"RATE_LIMIT_EXCEEDED\" || this.reason === \"transport\"\n }\n}\n\nfunction pick<T extends string>(value: unknown, allowed: readonly T[]): T | undefined {\n return typeof value === \"string\" && (allowed as readonly string[]).includes(value)\n ? (value as T)\n : undefined\n}\n\n/** Turn a non-2xx API response into a `LainternError`. Never throws itself. */\nexport async function errorFromResponse(response: Response): Promise<LainternError> {\n let code = `HTTP_${response.status}`\n let message = \"An unexpected error occurred.\"\n let rateLimitScope: \"user\" | \"agent\" | undefined\n let rateLimitWindow: \"minute\" | \"hour\" | \"day\" | undefined\n let reason: string | undefined\n\n try {\n const body = (await response.json()) as {\n error?: {\n code?: string\n message?: string\n scope?: unknown\n window?: unknown\n reason?: unknown\n }\n message?: string\n }\n if (body?.error?.code) {\n code = body.error.code\n message = body.error.message ?? message\n rateLimitScope = pick(body.error.scope, [\"user\", \"agent\"] as const)\n rateLimitWindow = pick(body.error.window, [\"minute\", \"hour\", \"day\"] as const)\n if (typeof body.error.reason === \"string\") reason = body.error.reason\n } else if (typeof body?.message === \"string\") {\n message = body.message\n }\n } catch {\n // Not JSON — keep the HTTP-derived defaults.\n }\n\n return new LainternError(code, message, {\n status: response.status,\n rateLimitScope,\n rateLimitWindow,\n reason,\n })\n}\n","import type { ChatStreamEvent } from \"@laintern-headless/types\"\n\n/** Sentinel that terminates the stream (OpenAI-style `data: [DONE]`). */\nconst DONE = \"[DONE]\"\n\n/**\n * Parse a Server-Sent Events response into a stream of chat events.\n *\n * The server writes one event per SSE frame (`data: <json>\\n\\n`) and ends with\n * a `data: [DONE]` sentinel. We read the raw `ReadableStream` from fetch\n * rather than using `EventSource`, which cannot send a POST body or an\n * Authorization header.\n */\nexport async function* parseSSE(response: Response): AsyncGenerator<ChatStreamEvent> {\n const reader = response.body?.getReader()\n if (!reader) throw new Error(\"Response body is not readable\")\n\n const decoder = new TextDecoder()\n let buffer = \"\"\n\n try {\n while (true) {\n const { done, value } = await reader.read()\n if (done) break\n\n buffer += decoder.decode(value, { stream: true })\n\n // SSE frames are separated by a blank line. Tolerate CRLF endings.\n let sepIndex: number\n while ((sepIndex = indexOfFrameBoundary(buffer)) !== -1) {\n const frame = buffer.slice(0, sepIndex)\n buffer = buffer.slice(boundaryEnd(buffer, sepIndex))\n const parsed = parseFrame(frame)\n if (parsed === DONE) return\n if (parsed) yield parsed\n }\n }\n\n // Flush a trailing frame that wasn't terminated by a blank line.\n const parsed = parseFrame(buffer)\n if (parsed && parsed !== DONE) yield parsed\n } finally {\n reader.releaseLock()\n }\n}\n\n/** Index of the start of the next `\\n\\n` or `\\r\\n\\r\\n` boundary, or -1. */\nfunction indexOfFrameBoundary(buffer: string): number {\n const lf = buffer.indexOf(\"\\n\\n\")\n const crlf = buffer.indexOf(\"\\r\\n\\r\\n\")\n if (lf === -1) return crlf\n if (crlf === -1) return lf\n return Math.min(lf, crlf)\n}\n\n/** Length of the boundary at `index` so we can slice past it. */\nfunction boundaryEnd(buffer: string, index: number): number {\n return buffer.startsWith(\"\\r\\n\\r\\n\", index) ? index + 4 : index + 2\n}\n\n/**\n * Extract the payload from a single SSE frame. Returns the parsed event, the\n * `DONE` sentinel, or `null` for empty/comment/unparseable frames.\n */\nfunction parseFrame(frame: string): ChatStreamEvent | typeof DONE | null {\n const trimmed = frame.trim()\n if (!trimmed) return null\n\n const dataLines: string[] = []\n for (const rawLine of trimmed.split(\"\\n\")) {\n const line = rawLine.replace(/\\r$/, \"\")\n if (line.startsWith(\":\")) continue // SSE comment\n if (!line.startsWith(\"data:\")) continue // ignore event:/id:/retry:\n dataLines.push(line.slice(5).replace(/^ /, \"\")) // strip \"data:\" + one optional space\n }\n if (dataLines.length === 0) return null\n\n const payload = dataLines.join(\"\\n\")\n if (payload === DONE) return DONE\n\n try {\n return JSON.parse(payload) as ChatStreamEvent\n } catch {\n return null\n }\n}\n","import { LainternError } from \"./errors.js\"\n\n/**\n * Refresh this long before a token expires. It mirrors the margin the API\n * applies when it clamps a chat token to a credential inside it: the server\n * guarantees the token dies *after* the credential's own refresh point, and\n * this buffer is what makes the client come back in time to use that.\n */\nexport const DEFAULT_REFRESH_BUFFER_MS = 60_000\n\n/** Used only when neither the JWT nor the endpoint says when the token dies. */\nconst FALLBACK_TTL_MS = 5 * 60 * 1000\n\n/** A token proxy that drifts further than this from the JWT is misconfigured. */\nconst DRIFT_WARN_THRESHOLD_MS = 5 * 60 * 1000\n\nexport interface TokenResponse {\n token: string\n /** ISO-8601 or epoch ms, if the endpoint reports one. */\n expiresAt?: string | number | null\n}\n\n/** Minimal storage surface — `sessionStorage`, `localStorage` or your own. */\nexport interface TokenStorage {\n getItem(key: string): string | null\n setItem(key: string, value: string): void\n removeItem(key: string): void\n}\n\nexport interface TokenManagerOptions {\n /**\n * Fetches a fresh token from YOUR backend. This is the one integration\n * point that cannot be generic: only your server may hold the API key that\n * mints Laintern tokens, and only it knows who the current user is.\n */\n fetchToken: () => Promise<TokenResponse | string>\n storage?: TokenStorage | null\n storageKey?: string\n refreshBufferMs?: number\n onWarning?: (message: string, detail?: Record<string, unknown>) => void\n}\n\nexport interface TokenManager {\n /** Cached token when still valid, otherwise a freshly fetched one. */\n get(): Promise<string>\n /** Drop the cached token — the next `get()` fetches. */\n clear(): void\n /** Expiry of the cached token, in epoch ms. Null when nothing is cached. */\n expiresAt(): number | null\n}\n\n/** Read a JWT's `exp` claim (epoch ms) without verifying the signature. */\nexport function decodeJwtExpiryMs(token: string): number | null {\n try {\n const parts = token.split(\".\")\n if (parts.length < 2) return null\n const padded = parts[1].replace(/-/g, \"+\").replace(/_/g, \"/\")\n // `atob` is global in browsers and in Node 18+, which is our floor. On an\n // exotic runtime without it we return null, and the caller falls back to\n // the endpoint's reported expiry.\n if (typeof atob !== \"function\") return null\n const json = JSON.parse(atob(padded)) as { exp?: unknown }\n return typeof json.exp === \"number\" ? json.exp * 1000 : null\n } catch {\n return null\n }\n}\n\nfunction toEpochMs(value: string | number | null | undefined): number | null {\n if (value == null) return null\n if (typeof value === \"number\") return Number.isFinite(value) ? value : null\n const parsed = Date.parse(value)\n return Number.isNaN(parsed) ? null : parsed\n}\n\n/**\n * Decide when a token really expires.\n *\n * The JWT's own `exp` wins: it is what the API enforces. A token endpoint that\n * reports something materially different is a misconfiguration worth\n * surfacing — it usually means the proxy invents its own TTL instead of\n * passing through what Laintern returned.\n */\nexport function resolveExpiry(\n token: string,\n reportedExpiresAt: string | number | null | undefined,\n onWarning?: TokenManagerOptions[\"onWarning\"],\n): number {\n const jwtExpMs = decodeJwtExpiryMs(token)\n const reported = toEpochMs(reportedExpiresAt)\n\n if (jwtExpMs !== null) {\n if (reported !== null && Math.abs(reported - jwtExpMs) > DRIFT_WARN_THRESHOLD_MS) {\n onWarning?.(\n \"Token endpoint 'expires_at' drifts more than 5 minutes from the JWT 'exp'; using the JWT claim.\",\n { jwtExpMs, reportedExpiresAtMs: reported, driftMs: reported - jwtExpMs },\n )\n }\n return jwtExpMs\n }\n if (reported !== null) return reported\n\n onWarning?.(\n \"Token has no readable 'exp' claim and the endpoint reported no expiry; caching for 5 minutes.\",\n )\n return Date.now() + FALLBACK_TTL_MS\n}\n\ninterface CachedToken {\n token: string\n expiresAt: number\n}\n\n/**\n * Caches the chat token and refreshes it before it dies.\n *\n * Concurrent callers share one in-flight fetch: a page that opens a stream\n * while another request is already refreshing must not mint two sessions.\n */\nexport function createTokenManager(options: TokenManagerOptions): TokenManager {\n const buffer = options.refreshBufferMs ?? DEFAULT_REFRESH_BUFFER_MS\n const storage = options.storage\n const storageKey = options.storageKey ?? \"laintern.token\"\n let cached: CachedToken | null = readStored()\n let inFlight: Promise<string> | null = null\n\n function readStored(): CachedToken | null {\n if (!storage) return null\n try {\n const raw = storage.getItem(storageKey)\n if (!raw) return null\n const parsed = JSON.parse(raw) as Partial<CachedToken>\n if (typeof parsed.token !== \"string\" || typeof parsed.expiresAt !== \"number\") {\n return null\n }\n return { token: parsed.token, expiresAt: parsed.expiresAt }\n } catch {\n return null\n }\n }\n\n function writeStored(value: CachedToken | null): void {\n if (!storage) return\n try {\n if (value === null) storage.removeItem(storageKey)\n else storage.setItem(storageKey, JSON.stringify(value))\n } catch {\n // Storage can be unavailable (private mode, blocked cookies). Non-fatal:\n // we simply refetch on the next page load.\n }\n }\n\n function isUsable(value: CachedToken | null): value is CachedToken {\n return value !== null && Date.now() < value.expiresAt - buffer\n }\n\n async function refresh(): Promise<string> {\n let result: TokenResponse | string\n try {\n result = await options.fetchToken()\n } catch (err) {\n throw new LainternError(\"TOKEN_FETCH_FAILED\", \"Could not obtain a chat token.\", {\n cause: err,\n })\n }\n const response: TokenResponse = typeof result === \"string\" ? { token: result } : result\n if (!response || typeof response.token !== \"string\" || response.token.length === 0) {\n throw new LainternError(\n \"TOKEN_INVALID\",\n \"Token endpoint returned no 'token' string.\",\n )\n }\n const next: CachedToken = {\n token: response.token,\n expiresAt: resolveExpiry(response.token, response.expiresAt, options.onWarning),\n }\n cached = next\n writeStored(next)\n return next.token\n }\n\n return {\n async get(): Promise<string> {\n if (isUsable(cached)) return cached.token\n if (!inFlight) {\n inFlight = refresh().finally(() => {\n inFlight = null\n })\n }\n return inFlight\n },\n clear(): void {\n cached = null\n writeStored(null)\n },\n expiresAt(): number | null {\n return cached?.expiresAt ?? null\n },\n }\n}\n","import type { ChatStreamEvent } from \"@laintern-headless/types\"\nimport { LainternError, errorFromResponse } from \"./errors.js\"\nimport { parseSSE } from \"./sse.js\"\nimport { createTokenManager, type TokenManager, type TokenManagerOptions } from \"./tokens.js\"\n\nexport interface LainternClientOptions {\n /** Base URL of the Laintern API, e.g. \"https://api.laintern.com\". */\n apiBase: string\n /**\n * How to get a chat token. Either your own function, or the URL of an\n * endpoint on your backend that returns `{ token, expires_at }`.\n *\n * Never put a Laintern API key in the browser: the key mints tokens for any\n * user, so it belongs on your server, behind your own session check.\n */\n token: TokenManagerOptions[\"fetchToken\"] | { endpoint: string; init?: RequestInit }\n storage?: TokenManagerOptions[\"storage\"]\n refreshBufferMs?: number\n onWarning?: TokenManagerOptions[\"onWarning\"]\n /** Injectable for tests and non-browser runtimes. */\n fetch?: typeof globalThis.fetch\n}\n\nexport interface SendMessageOptions {\n /** Continues an existing conversation. Omit to start a new one. */\n sessionId?: string | null\n /** Base64 data URLs, for agents with image input enabled. */\n images?: string[]\n /** Force a specific tool on this turn, when the agent allows it. */\n forceTool?: string\n signal?: AbortSignal\n}\n\nexport interface AgentConfig {\n /** Transparency notice to show before the conversation starts (EU AI Act). */\n disclaimer: { enabled: boolean; text: string | null }\n}\n\nexport interface LainternClient {\n /**\n * Send one message and iterate the response as it streams.\n *\n * Yields typed events (`delta`, `citation`, `sources`, `step`, `done`, …).\n * Prefer {@link createConversation} unless you want to assemble the turn\n * yourself.\n */\n sendMessage(message: string, options?: SendMessageOptions): AsyncGenerator<ChatStreamEvent>\n /** Per-agent presentation config. Best-effort: throws on transport errors. */\n getConfig(): Promise<AgentConfig>\n submitFeedback(messageId: string, feedback: \"positive\" | \"negative\"): Promise<void>\n /** The token cache, exposed so a host can clear it on logout. */\n tokens: TokenManager\n}\n\nfunction tokenFetcher(\n option: LainternClientOptions[\"token\"],\n doFetch: typeof globalThis.fetch,\n): TokenManagerOptions[\"fetchToken\"] {\n if (typeof option === \"function\") return option\n return async () => {\n const res = await doFetch(option.endpoint, {\n method: \"POST\",\n credentials: \"same-origin\",\n headers: { \"Content-Type\": \"application/json\" },\n ...option.init,\n })\n if (!res.ok) throw await errorFromResponse(res)\n const body = (await res.json()) as Record<string, unknown>\n const payload = ((body.data as Record<string, unknown>) ?? body) ?? {}\n return {\n token: payload.token as string,\n expiresAt: (payload.expires_at as string | undefined) ?? null,\n }\n }\n}\n\nexport function createLainternClient(options: LainternClientOptions): LainternClient {\n const doFetch = options.fetch ?? globalThis.fetch.bind(globalThis)\n const apiBase = options.apiBase.replace(/\\/+$/, \"\")\n const tokens = createTokenManager({\n fetchToken: tokenFetcher(options.token, doFetch),\n storage: options.storage,\n ...(options.refreshBufferMs !== undefined\n ? { refreshBufferMs: options.refreshBufferMs }\n : {}),\n ...(options.onWarning ? { onWarning: options.onWarning } : {}),\n })\n\n async function authorized(path: string, init: RequestInit = {}): Promise<Response> {\n const token = await tokens.get()\n return doFetch(`${apiBase}${path}`, {\n ...init,\n headers: {\n \"Content-Type\": \"application/json\",\n ...init.headers,\n Authorization: `Bearer ${token}`,\n },\n })\n }\n\n return {\n tokens,\n\n async *sendMessage(message, sendOptions = {}) {\n const body: Record<string, unknown> = { message }\n if (sendOptions.sessionId) body.session_id = sendOptions.sessionId\n if (sendOptions.images?.length) body.images = sendOptions.images\n if (sendOptions.forceTool) body.force_tool = sendOptions.forceTool\n\n let response: Response\n try {\n response = await authorized(\"/v1/chat\", {\n method: \"POST\",\n body: JSON.stringify(body),\n ...(sendOptions.signal ? { signal: sendOptions.signal } : {}),\n })\n } catch (err) {\n if (err instanceof LainternError) throw err\n if ((err as Error)?.name === \"AbortError\") throw err\n throw new LainternError(\n \"NETWORK_ERROR\",\n err instanceof Error ? err.message : \"Network request failed.\",\n { cause: err },\n )\n }\n\n if (!response.ok) {\n const error = await errorFromResponse(response)\n // A token can expire between mint and use — drop it so the next send\n // fetches a fresh one instead of replaying the dead credential.\n if (response.status === 401) tokens.clear()\n throw error\n }\n\n yield* parseSSE(response)\n },\n\n async getConfig(): Promise<AgentConfig> {\n const res = await authorized(\"/v1/config\", { method: \"GET\" })\n if (!res.ok) throw await errorFromResponse(res)\n const body = (await res.json()) as Record<string, unknown>\n const payload = ((body.data as Record<string, unknown>) ?? body) ?? {}\n const raw = payload.disclaimer as Record<string, unknown> | undefined\n return {\n disclaimer: {\n enabled: raw?.enabled !== false,\n text:\n typeof raw?.text === \"string\" && raw.text.trim().length > 0\n ? (raw.text as string)\n : null,\n },\n }\n },\n\n async submitFeedback(messageId, feedback): Promise<void> {\n const res = await authorized(`/v1/chat/messages/${messageId}/feedback`, {\n method: \"PATCH\",\n body: JSON.stringify({ feedback }),\n })\n if (!res.ok) throw await errorFromResponse(res)\n },\n }\n}\n","import type {\n CitationSource,\n ExternalSourceType,\n SearchResult,\n StepEvent,\n} from \"@laintern-headless/types\"\nimport type { LainternClient } from \"./client.js\"\nimport { LainternError } from \"./errors.js\"\n\n/** Soft advisory alongside an answer — the agent flagged the topic, but answered. */\nexport interface TurnAdvisory {\n redirect: string | null\n message: string\n}\n\n/** A guardrail refused the turn. The agent produced no answer. */\nexport interface TurnBlock {\n /** Which guardrail fired, when the server names one. */\n slug: string | null\n /** Copy meant for the end user, written by the guardrail's configuration. */\n message: string\n}\n\nexport interface TurnError {\n code: string\n message: string\n rateLimitScope?: \"user\" | \"agent\"\n rateLimitWindow?: \"minute\" | \"hour\" | \"day\"\n}\n\nexport interface Turn {\n id: string\n userMessage: string\n userImages?: string[]\n assistantContent: string\n /** Citation number (the `[1]` in the text) → the source it points at. */\n citations: Map<number, CitationSource>\n /** Everything the agent cited or consulted, in the order the server ranked it. */\n sources: SearchResult[]\n /** Sidebar suggestions per content type — not necessarily cited. */\n externalSources: Partial<Record<ExternalSourceType, SearchResult[]>>\n mode: string | null\n steps: StepEvent[]\n isStreaming: boolean\n error?: TurnError\n blocked?: TurnBlock\n advisory?: TurnAdvisory\n messageId?: string\n feedback?: \"positive\" | \"negative\" | null\n}\n\nexport interface ConversationState {\n turns: Turn[]\n sessionId: string | null\n isStreaming: boolean\n}\n\nexport interface SendOptions {\n images?: string[]\n forceTool?: string\n}\n\nexport interface Conversation {\n getState(): ConversationState\n /** Subscribe to state changes. Returns an unsubscribe function. */\n subscribe(listener: (state: ConversationState) => void): () => void\n /** Send a message and stream the answer into state. Never throws. */\n send(message: string, options?: SendOptions): Promise<void>\n /** Stop the current stream. The partial answer stays in state. */\n abort(): void\n /** Drop all turns and start a new server-side session on the next send. */\n reset(): void\n setFeedback(turnId: string, feedback: \"positive\" | \"negative\"): Promise<void>\n}\n\nexport interface ConversationOptions {\n /** Resume an existing server-side session. */\n sessionId?: string | null\n /** Restore turns from your own storage. */\n initialTurns?: Turn[]\n /** Called whenever the session id changes, so you can persist it. */\n onSessionId?: (sessionId: string) => void\n}\n\nfunction newId(): string {\n const c = globalThis.crypto\n if (c && typeof c.randomUUID === \"function\") return c.randomUUID()\n return `turn-${Date.now()}-${Math.random().toString(36).slice(2, 10)}`\n}\n\nfunction createTurn(message: string, images?: string[]): Turn {\n return {\n id: newId(),\n userMessage: message,\n ...(images?.length ? { userImages: images } : {}),\n assistantContent: \"\",\n citations: new Map(),\n sources: [],\n externalSources: {},\n mode: null,\n steps: [],\n isStreaming: true,\n }\n}\n\n/**\n * Stateful conversation on top of {@link LainternClient}.\n *\n * Holds the turn list, applies each stream event to the turn in flight, and\n * notifies subscribers. Framework-agnostic on purpose: the React binding in\n * `@laintern/chat-sdk/react` is a thin `useSyncExternalStore` wrapper, and any\n * other UI layer can subscribe the same way.\n *\n * `send()` never throws. A failed turn is part of the conversation — it lands\n * on `turn.error` (or `turn.blocked` for a guardrail) so the UI can render it\n * in place, which is what you want in a chat transcript.\n */\nexport function createConversation(\n client: LainternClient,\n options: ConversationOptions = {},\n): Conversation {\n let state: ConversationState = {\n turns: options.initialTurns ?? [],\n sessionId: options.sessionId ?? null,\n isStreaming: false,\n }\n const listeners = new Set<(state: ConversationState) => void>()\n let controller: AbortController | null = null\n\n function emit(next: ConversationState): void {\n state = next\n for (const listener of listeners) listener(state)\n }\n\n /** Apply a change to the turn currently streaming (always the last one). */\n function patchLast(patch: (turn: Turn) => Turn): void {\n const lastIndex = state.turns.length - 1\n if (lastIndex < 0) return\n const turns = [...state.turns]\n turns[lastIndex] = patch(turns[lastIndex])\n emit({ ...state, turns })\n }\n\n return {\n getState: () => state,\n\n subscribe(listener) {\n listeners.add(listener)\n return () => listeners.delete(listener)\n },\n\n abort() {\n controller?.abort()\n },\n\n reset() {\n controller?.abort()\n emit({ turns: [], sessionId: null, isStreaming: false })\n },\n\n async send(message, sendOptions = {}) {\n controller?.abort()\n controller = new AbortController()\n const signal = controller.signal\n\n emit({\n ...state,\n turns: [...state.turns, createTurn(message, sendOptions.images)],\n isStreaming: true,\n })\n\n try {\n const stream = client.sendMessage(message, {\n sessionId: state.sessionId,\n ...(sendOptions.images ? { images: sendOptions.images } : {}),\n ...(sendOptions.forceTool ? { forceTool: sendOptions.forceTool } : {}),\n signal,\n })\n\n for await (const event of stream) {\n if (signal.aborted) break\n\n switch (event.type) {\n case \"session\":\n emit({ ...state, sessionId: event.session_id })\n options.onSessionId?.(event.session_id)\n break\n case \"mode\":\n patchLast((turn) => ({ ...turn, mode: event.mode }))\n break\n case \"step\":\n patchLast((turn) => ({ ...turn, steps: mergeStep(turn.steps, event.step) }))\n break\n case \"delta\":\n patchLast((turn) => ({\n ...turn,\n assistantContent: turn.assistantContent + event.content,\n }))\n break\n case \"citation\":\n patchLast((turn) => {\n const citations = new Map(turn.citations)\n citations.set(event.index, event.source)\n return { ...turn, citations }\n })\n break\n case \"sources\":\n patchLast((turn) => ({ ...turn, sources: event.items }))\n break\n case \"external_sources\":\n patchLast((turn) => ({\n ...turn,\n externalSources: {\n ...turn.externalSources,\n [event.source_type]: event.items,\n },\n }))\n break\n case \"advisory\":\n patchLast((turn) => ({\n ...turn,\n advisory: { redirect: event.redirect ?? null, message: event.message },\n }))\n break\n case \"done\":\n patchLast((turn) => ({\n ...turn,\n isStreaming: false,\n ...(event.message_id ? { messageId: event.message_id } : {}),\n }))\n emit({ ...state, isStreaming: false })\n break\n case \"error\":\n applyFailure(patchLast, {\n code: event.code,\n message: event.message,\n guardrailSlug: event.guardrail_slug ?? null,\n })\n break\n }\n }\n } catch (err) {\n if ((err as Error)?.name === \"AbortError\") {\n patchLast((turn) => ({ ...turn, isStreaming: false }))\n } else if (err instanceof LainternError) {\n applyFailure(patchLast, {\n code: err.code,\n message: err.message,\n guardrailSlug: null,\n ...(err.rateLimitScope ? { rateLimitScope: err.rateLimitScope } : {}),\n ...(err.rateLimitWindow ? { rateLimitWindow: err.rateLimitWindow } : {}),\n })\n } else {\n applyFailure(patchLast, {\n code: \"NETWORK_ERROR\",\n message: err instanceof Error ? err.message : \"An unknown error occurred.\",\n guardrailSlug: null,\n })\n }\n } finally {\n // A turn always stops streaming, however it ended — including an abort\n // that broke the loop without throwing. Leaving the flag set strands a\n // typing indicator on a turn that will never produce another token.\n const last = state.turns.at(-1)\n if (last?.isStreaming) patchLast((turn) => ({ ...turn, isStreaming: false }))\n if (state.isStreaming) emit({ ...state, isStreaming: false })\n controller = null\n }\n },\n\n async setFeedback(turnId, feedback) {\n const turn = state.turns.find((t) => t.id === turnId)\n if (!turn?.messageId) {\n throw new LainternError(\n \"FEEDBACK_UNAVAILABLE\",\n \"This turn has no message id yet — feedback is only possible once a turn completes.\",\n )\n }\n await client.submitFeedback(turn.messageId, feedback)\n emit({\n ...state,\n turns: state.turns.map((t) => (t.id === turnId ? { ...t, feedback } : t)),\n })\n },\n }\n}\n\n/**\n * A completed step replaces its own \"started\" entry rather than appending, so\n * the UI shows one row per tool call that fills in, not two.\n */\nfunction mergeStep(steps: StepEvent[], step: StepEvent): StepEvent[] {\n if (step.status !== \"completed\") return [...steps, step]\n const startedIndex = steps.findIndex((s) => s.tool === step.tool && s.status === \"started\")\n if (startedIndex === -1) return [...steps, step]\n const next = [...steps]\n next[startedIndex] = step\n return next\n}\n\n/**\n * A failed or blocked turn.\n *\n * A guardrail block discards whatever streamed before it fired: the partial\n * answer is exactly the content the guardrail decided the user should not see,\n * and a hard block always wins over a soft advisory that arrived a moment\n * earlier.\n */\nfunction applyFailure(\n patchLast: (patch: (turn: Turn) => Turn) => void,\n failure: {\n code: string\n message: string\n guardrailSlug: string | null\n rateLimitScope?: \"user\" | \"agent\"\n rateLimitWindow?: \"minute\" | \"hour\" | \"day\"\n },\n): void {\n if (failure.code === \"GUARDRAIL_TRIGGERED\") {\n patchLast((turn) => ({\n ...turn,\n isStreaming: false,\n assistantContent: \"\",\n citations: new Map(),\n sources: [],\n externalSources: {},\n steps: [],\n mode: null,\n advisory: undefined,\n blocked: { slug: failure.guardrailSlug, message: failure.message },\n }))\n return\n }\n\n patchLast((turn) => ({\n ...turn,\n isStreaming: false,\n error: {\n code: failure.code,\n message: failure.message,\n ...(failure.rateLimitScope ? { rateLimitScope: failure.rateLimitScope } : {}),\n ...(failure.rateLimitWindow ? { rateLimitWindow: failure.rateLimitWindow } : {}),\n },\n }))\n}\n"]}
1
+ {"version":3,"sources":["../src/errors.ts","../src/sse.ts","../src/tokens.ts","../src/client.ts","../src/conversation.ts","../src/citations.ts"],"names":["parsed"],"mappings":"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","file":"index.cjs","sourcesContent":["/**\n * Every failure the SDK surfaces, with the API's own error code attached.\n *\n * Callers switch on `code` rather than parsing messages: the server's copy is\n * user-facing prose that may change, while the code is part of the contract.\n */\nexport class LainternError extends Error {\n readonly code: string\n readonly status: number | null\n /** Set on RATE_LIMIT_EXCEEDED. */\n readonly rateLimitScope?: \"user\" | \"agent\"\n readonly rateLimitWindow?: \"minute\" | \"hour\" | \"day\"\n /** Set on MCP_UNAVAILABLE: which dependency failed, and how. */\n readonly reason?: string\n\n constructor(\n code: string,\n message: string,\n options: {\n status?: number | null\n rateLimitScope?: \"user\" | \"agent\"\n rateLimitWindow?: \"minute\" | \"hour\" | \"day\"\n reason?: string\n cause?: unknown\n } = {},\n ) {\n super(message, options.cause !== undefined ? { cause: options.cause } : undefined)\n this.name = \"LainternError\"\n this.code = code\n this.status = options.status ?? null\n if (options.rateLimitScope) this.rateLimitScope = options.rateLimitScope\n if (options.rateLimitWindow) this.rateLimitWindow = options.rateLimitWindow\n if (options.reason) this.reason = options.reason\n }\n\n /**\n * True when retrying later could plausibly succeed: a rate limit, or a\n * dependency the server could not reach. An expired session is NOT retryable\n * — the host has to mint a new token first.\n */\n get isRetryable(): boolean {\n return this.code === \"RATE_LIMIT_EXCEEDED\" || this.reason === \"transport\"\n }\n}\n\nfunction pick<T extends string>(value: unknown, allowed: readonly T[]): T | undefined {\n return typeof value === \"string\" && (allowed as readonly string[]).includes(value)\n ? (value as T)\n : undefined\n}\n\n/** Turn a non-2xx API response into a `LainternError`. Never throws itself. */\nexport async function errorFromResponse(response: Response): Promise<LainternError> {\n let code = `HTTP_${response.status}`\n let message = \"An unexpected error occurred.\"\n let rateLimitScope: \"user\" | \"agent\" | undefined\n let rateLimitWindow: \"minute\" | \"hour\" | \"day\" | undefined\n let reason: string | undefined\n\n try {\n const body = (await response.json()) as {\n error?: {\n code?: string\n message?: string\n scope?: unknown\n window?: unknown\n reason?: unknown\n }\n message?: string\n }\n if (body?.error?.code) {\n code = body.error.code\n message = body.error.message ?? message\n rateLimitScope = pick(body.error.scope, [\"user\", \"agent\"] as const)\n rateLimitWindow = pick(body.error.window, [\"minute\", \"hour\", \"day\"] as const)\n if (typeof body.error.reason === \"string\") reason = body.error.reason\n } else if (typeof body?.message === \"string\") {\n message = body.message\n }\n } catch {\n // Not JSON — keep the HTTP-derived defaults.\n }\n\n return new LainternError(code, message, {\n status: response.status,\n rateLimitScope,\n rateLimitWindow,\n reason,\n })\n}\n","import type { ChatStreamEvent } from \"@laintern-headless/types\"\n\n/** Sentinel that terminates the stream (OpenAI-style `data: [DONE]`). */\nconst DONE = \"[DONE]\"\n\n/**\n * Parse a Server-Sent Events response into a stream of chat events.\n *\n * The server writes one event per SSE frame (`data: <json>\\n\\n`) and ends with\n * a `data: [DONE]` sentinel. We read the raw `ReadableStream` from fetch\n * rather than using `EventSource`, which cannot send a POST body or an\n * Authorization header.\n */\nexport async function* parseSSE(response: Response): AsyncGenerator<ChatStreamEvent> {\n const reader = response.body?.getReader()\n if (!reader) throw new Error(\"Response body is not readable\")\n\n const decoder = new TextDecoder()\n let buffer = \"\"\n\n try {\n while (true) {\n const { done, value } = await reader.read()\n if (done) break\n\n buffer += decoder.decode(value, { stream: true })\n\n // SSE frames are separated by a blank line. Tolerate CRLF endings.\n let sepIndex: number\n while ((sepIndex = indexOfFrameBoundary(buffer)) !== -1) {\n const frame = buffer.slice(0, sepIndex)\n buffer = buffer.slice(boundaryEnd(buffer, sepIndex))\n const parsed = parseFrame(frame)\n if (parsed === DONE) return\n if (parsed) yield parsed\n }\n }\n\n // Flush a trailing frame that wasn't terminated by a blank line.\n const parsed = parseFrame(buffer)\n if (parsed && parsed !== DONE) yield parsed\n } finally {\n reader.releaseLock()\n }\n}\n\n/** Index of the start of the next `\\n\\n` or `\\r\\n\\r\\n` boundary, or -1. */\nfunction indexOfFrameBoundary(buffer: string): number {\n const lf = buffer.indexOf(\"\\n\\n\")\n const crlf = buffer.indexOf(\"\\r\\n\\r\\n\")\n if (lf === -1) return crlf\n if (crlf === -1) return lf\n return Math.min(lf, crlf)\n}\n\n/** Length of the boundary at `index` so we can slice past it. */\nfunction boundaryEnd(buffer: string, index: number): number {\n return buffer.startsWith(\"\\r\\n\\r\\n\", index) ? index + 4 : index + 2\n}\n\n/**\n * Extract the payload from a single SSE frame. Returns the parsed event, the\n * `DONE` sentinel, or `null` for empty/comment/unparseable frames.\n */\nfunction parseFrame(frame: string): ChatStreamEvent | typeof DONE | null {\n const trimmed = frame.trim()\n if (!trimmed) return null\n\n const dataLines: string[] = []\n for (const rawLine of trimmed.split(\"\\n\")) {\n const line = rawLine.replace(/\\r$/, \"\")\n if (line.startsWith(\":\")) continue // SSE comment\n if (!line.startsWith(\"data:\")) continue // ignore event:/id:/retry:\n dataLines.push(line.slice(5).replace(/^ /, \"\")) // strip \"data:\" + one optional space\n }\n if (dataLines.length === 0) return null\n\n const payload = dataLines.join(\"\\n\")\n if (payload === DONE) return DONE\n\n try {\n return JSON.parse(payload) as ChatStreamEvent\n } catch {\n return null\n }\n}\n","import { LainternError } from \"./errors.js\"\n\n/**\n * Refresh this long before a token expires. It mirrors the margin the API\n * applies when it clamps a chat token to a credential inside it: the server\n * guarantees the token dies *after* the credential's own refresh point, and\n * this buffer is what makes the client come back in time to use that.\n */\nexport const DEFAULT_REFRESH_BUFFER_MS = 60_000\n\n/** Used only when neither the JWT nor the endpoint says when the token dies. */\nconst FALLBACK_TTL_MS = 5 * 60 * 1000\n\n/** A token proxy that drifts further than this from the JWT is misconfigured. */\nconst DRIFT_WARN_THRESHOLD_MS = 5 * 60 * 1000\n\nexport interface TokenResponse {\n token: string\n /** ISO-8601 or epoch ms, if the endpoint reports one. */\n expiresAt?: string | number | null\n}\n\n/** Minimal storage surface — `sessionStorage`, `localStorage` or your own. */\nexport interface TokenStorage {\n getItem(key: string): string | null\n setItem(key: string, value: string): void\n removeItem(key: string): void\n}\n\nexport interface TokenManagerOptions {\n /**\n * Fetches a fresh token from YOUR backend. This is the one integration\n * point that cannot be generic: only your server may hold the API key that\n * mints Laintern tokens, and only it knows who the current user is.\n */\n fetchToken: () => Promise<TokenResponse | string>\n storage?: TokenStorage | null\n storageKey?: string\n refreshBufferMs?: number\n onWarning?: (message: string, detail?: Record<string, unknown>) => void\n}\n\nexport interface TokenManager {\n /** Cached token when still valid, otherwise a freshly fetched one. */\n get(): Promise<string>\n /** Drop the cached token — the next `get()` fetches. */\n clear(): void\n /** Expiry of the cached token, in epoch ms. Null when nothing is cached. */\n expiresAt(): number | null\n}\n\n/** Read a JWT's `exp` claim (epoch ms) without verifying the signature. */\nexport function decodeJwtExpiryMs(token: string): number | null {\n try {\n const parts = token.split(\".\")\n if (parts.length < 2) return null\n const padded = parts[1].replace(/-/g, \"+\").replace(/_/g, \"/\")\n // `atob` is global in browsers and in Node 18+, which is our floor. On an\n // exotic runtime without it we return null, and the caller falls back to\n // the endpoint's reported expiry.\n if (typeof atob !== \"function\") return null\n const json = JSON.parse(atob(padded)) as { exp?: unknown }\n return typeof json.exp === \"number\" ? json.exp * 1000 : null\n } catch {\n return null\n }\n}\n\nfunction toEpochMs(value: string | number | null | undefined): number | null {\n if (value == null) return null\n if (typeof value === \"number\") return Number.isFinite(value) ? value : null\n const parsed = Date.parse(value)\n return Number.isNaN(parsed) ? null : parsed\n}\n\n/**\n * Decide when a token really expires.\n *\n * The JWT's own `exp` wins: it is what the API enforces. A token endpoint that\n * reports something materially different is a misconfiguration worth\n * surfacing — it usually means the proxy invents its own TTL instead of\n * passing through what Laintern returned.\n */\nexport function resolveExpiry(\n token: string,\n reportedExpiresAt: string | number | null | undefined,\n onWarning?: TokenManagerOptions[\"onWarning\"],\n): number {\n const jwtExpMs = decodeJwtExpiryMs(token)\n const reported = toEpochMs(reportedExpiresAt)\n\n if (jwtExpMs !== null) {\n if (reported !== null && Math.abs(reported - jwtExpMs) > DRIFT_WARN_THRESHOLD_MS) {\n onWarning?.(\n \"Token endpoint 'expires_at' drifts more than 5 minutes from the JWT 'exp'; using the JWT claim.\",\n { jwtExpMs, reportedExpiresAtMs: reported, driftMs: reported - jwtExpMs },\n )\n }\n return jwtExpMs\n }\n if (reported !== null) return reported\n\n onWarning?.(\n \"Token has no readable 'exp' claim and the endpoint reported no expiry; caching for 5 minutes.\",\n )\n return Date.now() + FALLBACK_TTL_MS\n}\n\ninterface CachedToken {\n token: string\n expiresAt: number\n}\n\n/**\n * Caches the chat token and refreshes it before it dies.\n *\n * Concurrent callers share one in-flight fetch: a page that opens a stream\n * while another request is already refreshing must not mint two sessions.\n */\nexport function createTokenManager(options: TokenManagerOptions): TokenManager {\n const buffer = options.refreshBufferMs ?? DEFAULT_REFRESH_BUFFER_MS\n const storage = options.storage\n const storageKey = options.storageKey ?? \"laintern.token\"\n let cached: CachedToken | null = readStored()\n let inFlight: Promise<string> | null = null\n\n function readStored(): CachedToken | null {\n if (!storage) return null\n try {\n const raw = storage.getItem(storageKey)\n if (!raw) return null\n const parsed = JSON.parse(raw) as Partial<CachedToken>\n if (typeof parsed.token !== \"string\" || typeof parsed.expiresAt !== \"number\") {\n return null\n }\n return { token: parsed.token, expiresAt: parsed.expiresAt }\n } catch {\n return null\n }\n }\n\n function writeStored(value: CachedToken | null): void {\n if (!storage) return\n try {\n if (value === null) storage.removeItem(storageKey)\n else storage.setItem(storageKey, JSON.stringify(value))\n } catch {\n // Storage can be unavailable (private mode, blocked cookies). Non-fatal:\n // we simply refetch on the next page load.\n }\n }\n\n function isUsable(value: CachedToken | null): value is CachedToken {\n return value !== null && Date.now() < value.expiresAt - buffer\n }\n\n async function refresh(): Promise<string> {\n let result: TokenResponse | string\n try {\n result = await options.fetchToken()\n } catch (err) {\n throw new LainternError(\"TOKEN_FETCH_FAILED\", \"Could not obtain a chat token.\", {\n cause: err,\n })\n }\n const response: TokenResponse = typeof result === \"string\" ? { token: result } : result\n if (!response || typeof response.token !== \"string\" || response.token.length === 0) {\n throw new LainternError(\n \"TOKEN_INVALID\",\n \"Token endpoint returned no 'token' string.\",\n )\n }\n const next: CachedToken = {\n token: response.token,\n expiresAt: resolveExpiry(response.token, response.expiresAt, options.onWarning),\n }\n cached = next\n writeStored(next)\n return next.token\n }\n\n return {\n async get(): Promise<string> {\n if (isUsable(cached)) return cached.token\n if (!inFlight) {\n inFlight = refresh().finally(() => {\n inFlight = null\n })\n }\n return inFlight\n },\n clear(): void {\n cached = null\n writeStored(null)\n },\n expiresAt(): number | null {\n return cached?.expiresAt ?? null\n },\n }\n}\n","import type { ChatStreamEvent } from \"@laintern-headless/types\"\nimport { LainternError, errorFromResponse } from \"./errors.js\"\nimport { parseSSE } from \"./sse.js\"\nimport { createTokenManager, type TokenManager, type TokenManagerOptions } from \"./tokens.js\"\n\nexport interface LainternClientOptions {\n /** Base URL of the Laintern API, e.g. \"https://api.laintern.com\". */\n apiBase: string\n /**\n * How to get a chat token. Either your own function, or the URL of an\n * endpoint on your backend that returns `{ token, expires_at }`.\n *\n * Never put a Laintern API key in the browser: the key mints tokens for any\n * user, so it belongs on your server, behind your own session check.\n */\n token: TokenManagerOptions[\"fetchToken\"] | { endpoint: string; init?: RequestInit }\n storage?: TokenManagerOptions[\"storage\"]\n refreshBufferMs?: number\n onWarning?: TokenManagerOptions[\"onWarning\"]\n /** Injectable for tests and non-browser runtimes. */\n fetch?: typeof globalThis.fetch\n}\n\nexport interface SendMessageOptions {\n /** Continues an existing conversation. Omit to start a new one. */\n sessionId?: string | null\n /** Base64 data URLs, for agents with image input enabled. */\n images?: string[]\n /** Force a specific tool on this turn, when the agent allows it. */\n forceTool?: string\n signal?: AbortSignal\n}\n\nexport interface AgentConfig {\n /** Transparency notice to show before the conversation starts (EU AI Act). */\n disclaimer: { enabled: boolean; text: string | null }\n}\n\nexport interface LainternClient {\n /**\n * Send one message and iterate the response as it streams.\n *\n * Yields typed events (`delta`, `citation`, `sources`, `step`, `done`, …).\n * Prefer {@link createConversation} unless you want to assemble the turn\n * yourself.\n */\n sendMessage(message: string, options?: SendMessageOptions): AsyncGenerator<ChatStreamEvent>\n /** Per-agent presentation config. Best-effort: throws on transport errors. */\n getConfig(): Promise<AgentConfig>\n submitFeedback(messageId: string, feedback: \"positive\" | \"negative\"): Promise<void>\n /** The token cache, exposed so a host can clear it on logout. */\n tokens: TokenManager\n}\n\nfunction tokenFetcher(\n option: LainternClientOptions[\"token\"],\n doFetch: typeof globalThis.fetch,\n): TokenManagerOptions[\"fetchToken\"] {\n if (typeof option === \"function\") return option\n return async () => {\n const res = await doFetch(option.endpoint, {\n method: \"POST\",\n credentials: \"same-origin\",\n headers: { \"Content-Type\": \"application/json\" },\n ...option.init,\n })\n if (!res.ok) throw await errorFromResponse(res)\n const body = (await res.json()) as Record<string, unknown>\n const payload = ((body.data as Record<string, unknown>) ?? body) ?? {}\n return {\n token: payload.token as string,\n expiresAt: (payload.expires_at as string | undefined) ?? null,\n }\n }\n}\n\nexport function createLainternClient(options: LainternClientOptions): LainternClient {\n const doFetch = options.fetch ?? globalThis.fetch.bind(globalThis)\n const apiBase = options.apiBase.replace(/\\/+$/, \"\")\n const tokens = createTokenManager({\n fetchToken: tokenFetcher(options.token, doFetch),\n storage: options.storage,\n ...(options.refreshBufferMs !== undefined\n ? { refreshBufferMs: options.refreshBufferMs }\n : {}),\n ...(options.onWarning ? { onWarning: options.onWarning } : {}),\n })\n\n async function authorized(path: string, init: RequestInit = {}): Promise<Response> {\n const token = await tokens.get()\n return doFetch(`${apiBase}${path}`, {\n ...init,\n headers: {\n \"Content-Type\": \"application/json\",\n ...init.headers,\n Authorization: `Bearer ${token}`,\n },\n })\n }\n\n return {\n tokens,\n\n async *sendMessage(message, sendOptions = {}) {\n const body: Record<string, unknown> = { message }\n if (sendOptions.sessionId) body.session_id = sendOptions.sessionId\n if (sendOptions.images?.length) body.images = sendOptions.images\n if (sendOptions.forceTool) body.force_tool = sendOptions.forceTool\n\n let response: Response\n try {\n response = await authorized(\"/v1/chat\", {\n method: \"POST\",\n body: JSON.stringify(body),\n ...(sendOptions.signal ? { signal: sendOptions.signal } : {}),\n })\n } catch (err) {\n if (err instanceof LainternError) throw err\n if ((err as Error)?.name === \"AbortError\") throw err\n throw new LainternError(\n \"NETWORK_ERROR\",\n err instanceof Error ? err.message : \"Network request failed.\",\n { cause: err },\n )\n }\n\n if (!response.ok) {\n const error = await errorFromResponse(response)\n // A token can expire between mint and use — drop it so the next send\n // fetches a fresh one instead of replaying the dead credential.\n if (response.status === 401) tokens.clear()\n throw error\n }\n\n yield* parseSSE(response)\n },\n\n async getConfig(): Promise<AgentConfig> {\n const res = await authorized(\"/v1/config\", { method: \"GET\" })\n if (!res.ok) throw await errorFromResponse(res)\n const body = (await res.json()) as Record<string, unknown>\n const payload = ((body.data as Record<string, unknown>) ?? body) ?? {}\n const raw = payload.disclaimer as Record<string, unknown> | undefined\n return {\n disclaimer: {\n enabled: raw?.enabled !== false,\n text:\n typeof raw?.text === \"string\" && raw.text.trim().length > 0\n ? (raw.text as string)\n : null,\n },\n }\n },\n\n async submitFeedback(messageId, feedback): Promise<void> {\n const res = await authorized(`/v1/chat/messages/${messageId}/feedback`, {\n method: \"PATCH\",\n body: JSON.stringify({ feedback }),\n })\n if (!res.ok) throw await errorFromResponse(res)\n },\n }\n}\n","import type {\n CitationSource,\n ExternalSourceType,\n SearchResult,\n StepEvent,\n} from \"@laintern-headless/types\"\nimport type { LainternClient } from \"./client.js\"\nimport { LainternError } from \"./errors.js\"\n\n/** Soft advisory alongside an answer — the agent flagged the topic, but answered. */\nexport interface TurnAdvisory {\n redirect: string | null\n message: string\n}\n\n/** A guardrail refused the turn. The agent produced no answer. */\nexport interface TurnBlock {\n /** Which guardrail fired, when the server names one. */\n slug: string | null\n /** Copy meant for the end user, written by the guardrail's configuration. */\n message: string\n}\n\nexport interface TurnError {\n code: string\n message: string\n rateLimitScope?: \"user\" | \"agent\"\n rateLimitWindow?: \"minute\" | \"hour\" | \"day\"\n}\n\nexport interface Turn {\n id: string\n userMessage: string\n userImages?: string[]\n assistantContent: string\n /** Citation number (the `[1]` in the text) → the source it points at. */\n citations: Map<number, CitationSource>\n /** Everything the agent cited or consulted, in the order the server ranked it. */\n sources: SearchResult[]\n /** Sidebar suggestions per content type — not necessarily cited. */\n externalSources: Partial<Record<ExternalSourceType, SearchResult[]>>\n mode: string | null\n steps: StepEvent[]\n isStreaming: boolean\n error?: TurnError\n blocked?: TurnBlock\n advisory?: TurnAdvisory\n messageId?: string\n feedback?: \"positive\" | \"negative\" | null\n}\n\nexport interface ConversationState {\n turns: Turn[]\n sessionId: string | null\n isStreaming: boolean\n}\n\nexport interface SendOptions {\n images?: string[]\n forceTool?: string\n}\n\nexport interface Conversation {\n getState(): ConversationState\n /** Subscribe to state changes. Returns an unsubscribe function. */\n subscribe(listener: (state: ConversationState) => void): () => void\n /** Send a message and stream the answer into state. Never throws. */\n send(message: string, options?: SendOptions): Promise<void>\n /** Stop the current stream. The partial answer stays in state. */\n abort(): void\n /** Drop all turns and start a new server-side session on the next send. */\n reset(): void\n setFeedback(turnId: string, feedback: \"positive\" | \"negative\"): Promise<void>\n}\n\nexport interface ConversationOptions {\n /** Resume an existing server-side session. */\n sessionId?: string | null\n /** Restore turns from your own storage. */\n initialTurns?: Turn[]\n /** Called whenever the session id changes, so you can persist it. */\n onSessionId?: (sessionId: string) => void\n}\n\nfunction newId(): string {\n const c = globalThis.crypto\n if (c && typeof c.randomUUID === \"function\") return c.randomUUID()\n return `turn-${Date.now()}-${Math.random().toString(36).slice(2, 10)}`\n}\n\nfunction createTurn(message: string, images?: string[]): Turn {\n return {\n id: newId(),\n userMessage: message,\n ...(images?.length ? { userImages: images } : {}),\n assistantContent: \"\",\n citations: new Map(),\n sources: [],\n externalSources: {},\n mode: null,\n steps: [],\n isStreaming: true,\n }\n}\n\n/**\n * Stateful conversation on top of {@link LainternClient}.\n *\n * Holds the turn list, applies each stream event to the turn in flight, and\n * notifies subscribers. Framework-agnostic on purpose: the React binding in\n * `@laintern/chat-sdk/react` is a thin `useSyncExternalStore` wrapper, and any\n * other UI layer can subscribe the same way.\n *\n * `send()` never throws. A failed turn is part of the conversation — it lands\n * on `turn.error` (or `turn.blocked` for a guardrail) so the UI can render it\n * in place, which is what you want in a chat transcript.\n */\nexport function createConversation(\n client: LainternClient,\n options: ConversationOptions = {},\n): Conversation {\n let state: ConversationState = {\n turns: options.initialTurns ?? [],\n sessionId: options.sessionId ?? null,\n isStreaming: false,\n }\n const listeners = new Set<(state: ConversationState) => void>()\n let controller: AbortController | null = null\n\n function emit(next: ConversationState): void {\n state = next\n for (const listener of listeners) listener(state)\n }\n\n /** Apply a change to the turn currently streaming (always the last one). */\n function patchLast(patch: (turn: Turn) => Turn): void {\n const lastIndex = state.turns.length - 1\n if (lastIndex < 0) return\n const turns = [...state.turns]\n turns[lastIndex] = patch(turns[lastIndex])\n emit({ ...state, turns })\n }\n\n return {\n getState: () => state,\n\n subscribe(listener) {\n listeners.add(listener)\n return () => listeners.delete(listener)\n },\n\n abort() {\n controller?.abort()\n },\n\n reset() {\n controller?.abort()\n emit({ turns: [], sessionId: null, isStreaming: false })\n },\n\n async send(message, sendOptions = {}) {\n controller?.abort()\n controller = new AbortController()\n const signal = controller.signal\n\n emit({\n ...state,\n turns: [...state.turns, createTurn(message, sendOptions.images)],\n isStreaming: true,\n })\n\n try {\n const stream = client.sendMessage(message, {\n sessionId: state.sessionId,\n ...(sendOptions.images ? { images: sendOptions.images } : {}),\n ...(sendOptions.forceTool ? { forceTool: sendOptions.forceTool } : {}),\n signal,\n })\n\n for await (const event of stream) {\n if (signal.aborted) break\n\n switch (event.type) {\n case \"session\":\n emit({ ...state, sessionId: event.session_id })\n options.onSessionId?.(event.session_id)\n break\n case \"mode\":\n patchLast((turn) => ({ ...turn, mode: event.mode }))\n break\n case \"step\":\n patchLast((turn) => ({ ...turn, steps: mergeStep(turn.steps, event.step) }))\n break\n case \"delta\":\n patchLast((turn) => ({\n ...turn,\n assistantContent: turn.assistantContent + event.content,\n }))\n break\n case \"citation\":\n patchLast((turn) => {\n const citations = new Map(turn.citations)\n citations.set(event.index, event.source)\n return { ...turn, citations }\n })\n break\n case \"sources\":\n patchLast((turn) => ({ ...turn, sources: event.items }))\n break\n case \"external_sources\":\n patchLast((turn) => ({\n ...turn,\n externalSources: {\n ...turn.externalSources,\n [event.source_type]: event.items,\n },\n }))\n break\n case \"advisory\":\n patchLast((turn) => ({\n ...turn,\n advisory: { redirect: event.redirect ?? null, message: event.message },\n }))\n break\n case \"done\":\n patchLast((turn) => ({\n ...turn,\n isStreaming: false,\n ...(event.message_id ? { messageId: event.message_id } : {}),\n }))\n emit({ ...state, isStreaming: false })\n break\n case \"error\":\n applyFailure(patchLast, {\n code: event.code,\n message: event.message,\n guardrailSlug: event.guardrail_slug ?? null,\n })\n break\n }\n }\n } catch (err) {\n if ((err as Error)?.name === \"AbortError\") {\n patchLast((turn) => ({ ...turn, isStreaming: false }))\n } else if (err instanceof LainternError) {\n applyFailure(patchLast, {\n code: err.code,\n message: err.message,\n guardrailSlug: null,\n ...(err.rateLimitScope ? { rateLimitScope: err.rateLimitScope } : {}),\n ...(err.rateLimitWindow ? { rateLimitWindow: err.rateLimitWindow } : {}),\n })\n } else {\n applyFailure(patchLast, {\n code: \"NETWORK_ERROR\",\n message: err instanceof Error ? err.message : \"An unknown error occurred.\",\n guardrailSlug: null,\n })\n }\n } finally {\n // A turn always stops streaming, however it ended — including an abort\n // that broke the loop without throwing. Leaving the flag set strands a\n // typing indicator on a turn that will never produce another token.\n const last = state.turns.at(-1)\n if (last?.isStreaming) patchLast((turn) => ({ ...turn, isStreaming: false }))\n if (state.isStreaming) emit({ ...state, isStreaming: false })\n controller = null\n }\n },\n\n async setFeedback(turnId, feedback) {\n const turn = state.turns.find((t) => t.id === turnId)\n if (!turn?.messageId) {\n throw new LainternError(\n \"FEEDBACK_UNAVAILABLE\",\n \"This turn has no message id yet — feedback is only possible once a turn completes.\",\n )\n }\n await client.submitFeedback(turn.messageId, feedback)\n emit({\n ...state,\n turns: state.turns.map((t) => (t.id === turnId ? { ...t, feedback } : t)),\n })\n },\n }\n}\n\n/**\n * A completed step replaces its own \"started\" entry rather than appending, so\n * the UI shows one row per tool call that fills in, not two.\n */\nfunction mergeStep(steps: StepEvent[], step: StepEvent): StepEvent[] {\n if (step.status !== \"completed\") return [...steps, step]\n const startedIndex = steps.findIndex((s) => s.tool === step.tool && s.status === \"started\")\n if (startedIndex === -1) return [...steps, step]\n const next = [...steps]\n next[startedIndex] = step\n return next\n}\n\n/**\n * A failed or blocked turn.\n *\n * A guardrail block discards whatever streamed before it fired: the partial\n * answer is exactly the content the guardrail decided the user should not see,\n * and a hard block always wins over a soft advisory that arrived a moment\n * earlier.\n */\nfunction applyFailure(\n patchLast: (patch: (turn: Turn) => Turn) => void,\n failure: {\n code: string\n message: string\n guardrailSlug: string | null\n rateLimitScope?: \"user\" | \"agent\"\n rateLimitWindow?: \"minute\" | \"hour\" | \"day\"\n },\n): void {\n if (failure.code === \"GUARDRAIL_TRIGGERED\") {\n patchLast((turn) => ({\n ...turn,\n isStreaming: false,\n assistantContent: \"\",\n citations: new Map(),\n sources: [],\n externalSources: {},\n steps: [],\n mode: null,\n advisory: undefined,\n blocked: { slug: failure.guardrailSlug, message: failure.message },\n }))\n return\n }\n\n patchLast((turn) => ({\n ...turn,\n isStreaming: false,\n error: {\n code: failure.code,\n message: failure.message,\n ...(failure.rateLimitScope ? { rateLimitScope: failure.rateLimitScope } : {}),\n ...(failure.rateLimitWindow ? { rateLimitWindow: failure.rateLimitWindow } : {}),\n },\n }))\n}\n","import type { CitationSource, SearchResult } from \"@laintern-headless/types\"\nimport type { Turn } from \"./conversation.js\"\n\n/** Source ids the server hands out are `source0`, `source1`, … in call order. */\nconst SOURCE_INDEX_RE = /^source(\\d+)$/\n\nfunction sourceIndex(source: SearchResult): number {\n const match = source.content_id ? SOURCE_INDEX_RE.exec(source.content_id) : null\n return match ? Number.parseInt(match[1], 10) : Number.POSITIVE_INFINITY\n}\n\nfunction fromSearchResult(source: SearchResult): CitationSource {\n return {\n source_id: source.content_id || source.url,\n title: source.title,\n url: source.url,\n content_type: source.content_type,\n ...(source.section ? { section: source.section } : {}),\n }\n}\n\n/**\n * Resolve a `[n]` marker in the answer text to the source it points at.\n *\n * The answer streams token by token, so a marker can render before its\n * `citation` event arrives. The fallback covers that gap by position: sources\n * are numbered in the order the agent collected them, so the n-th source is\n * the one the n-th marker means. Returns null when neither is available yet —\n * render the bare `[n]` and let it resolve on the next frame.\n */\nexport function resolveCitation(index: number, turn: Pick<Turn, \"citations\" | \"sources\">): CitationSource | null {\n const exact = turn.citations.get(index)\n if (exact) return exact\n if (index < 1 || turn.sources.length === 0) return null\n\n const ordered = [...turn.sources].sort((a, b) => sourceIndex(a) - sourceIndex(b))\n const fallback = ordered[index - 1]\n return fallback ? fromSearchResult(fallback) : null\n}\n\n/**\n * True when a citation has nowhere to link to — a knowledge-base passage\n * lives in the client's own document store, not on the public web. Render\n * those as text (title, plus `section` when present), never as a dead link.\n */\nexport function isNavigable(source: CitationSource): boolean {\n return typeof source.url === \"string\" && source.url.trim().length > 0\n}\n\n/** Splits the answer into text and citation markers, in order. */\nexport type AnswerSegment =\n | { type: \"text\"; text: string }\n | { type: \"citation\"; index: number; source: CitationSource | null }\n\nconst CITATION_PATTERN = /\\[(\\d+)\\]/g\n\n/**\n * Walk the answer text and hand back its segments, with each `[n]` already\n * resolved. Lets a UI render citations as components without writing its own\n * parser — and without turning the markdown into HTML first.\n */\nexport function segmentAnswer(\n content: string,\n turn: Pick<Turn, \"citations\" | \"sources\">,\n): AnswerSegment[] {\n const segments: AnswerSegment[] = []\n let lastIndex = 0\n\n for (const match of content.matchAll(CITATION_PATTERN)) {\n const at = match.index ?? 0\n if (at > lastIndex) segments.push({ type: \"text\", text: content.slice(lastIndex, at) })\n const index = Number.parseInt(match[1], 10)\n segments.push({ type: \"citation\", index, source: resolveCitation(index, turn) })\n lastIndex = at + match[0].length\n }\n if (lastIndex < content.length) {\n segments.push({ type: \"text\", text: content.slice(lastIndex) })\n }\n return segments\n}\n"]}
package/dist/index.d.cts CHANGED
@@ -1,5 +1,5 @@
1
- import { C as ChatStreamEvent } from './conversation-7VzVDT95.cjs';
2
- export { A as AgentConfig, a as ChatContentType, b as CitationSource, c as ContentType, d as Conversation, e as ConversationOptions, f as ConversationState, D as DEFAULT_REFRESH_BUFFER_MS, E as ExternalSourceType, L as LainternClient, g as LainternClientOptions, S as SearchResult, h as SendMessageOptions, i as SendOptions, j as StepEvent, T as TokenManager, k as TokenManagerOptions, l as TokenResponse, m as TokenStorage, n as Turn, o as TurnAdvisory, p as TurnBlock, q as TurnError, r as createConversation, s as createLainternClient, t as createTokenManager, u as decodeJwtExpiryMs } from './conversation-7VzVDT95.cjs';
1
+ import { C as ChatStreamEvent } from './citations-BR_LD7jF.cjs';
2
+ export { A as AgentConfig, a as AnswerSegment, b as ChatContentType, c as CitationSource, d as ContentType, e as Conversation, f as ConversationOptions, g as ConversationState, D as DEFAULT_REFRESH_BUFFER_MS, E as ExternalSourceType, L as LainternClient, h as LainternClientOptions, S as SearchResult, i as SendMessageOptions, j as SendOptions, k as StepEvent, T as TokenManager, l as TokenManagerOptions, m as TokenResponse, n as TokenStorage, o as Turn, p as TurnAdvisory, q as TurnBlock, r as TurnError, s as createConversation, t as createLainternClient, u as createTokenManager, v as decodeJwtExpiryMs, w as isNavigable, x as resolveCitation, y as segmentAnswer } from './citations-BR_LD7jF.cjs';
3
3
 
4
4
  /**
5
5
  * Every failure the SDK surfaces, with the API's own error code attached.
package/dist/index.d.ts CHANGED
@@ -1,5 +1,5 @@
1
- import { C as ChatStreamEvent } from './conversation-7VzVDT95.js';
2
- export { A as AgentConfig, a as ChatContentType, b as CitationSource, c as ContentType, d as Conversation, e as ConversationOptions, f as ConversationState, D as DEFAULT_REFRESH_BUFFER_MS, E as ExternalSourceType, L as LainternClient, g as LainternClientOptions, S as SearchResult, h as SendMessageOptions, i as SendOptions, j as StepEvent, T as TokenManager, k as TokenManagerOptions, l as TokenResponse, m as TokenStorage, n as Turn, o as TurnAdvisory, p as TurnBlock, q as TurnError, r as createConversation, s as createLainternClient, t as createTokenManager, u as decodeJwtExpiryMs } from './conversation-7VzVDT95.js';
1
+ import { C as ChatStreamEvent } from './citations-BR_LD7jF.js';
2
+ export { A as AgentConfig, a as AnswerSegment, b as ChatContentType, c as CitationSource, d as ContentType, e as Conversation, f as ConversationOptions, g as ConversationState, D as DEFAULT_REFRESH_BUFFER_MS, E as ExternalSourceType, L as LainternClient, h as LainternClientOptions, S as SearchResult, i as SendMessageOptions, j as SendOptions, k as StepEvent, T as TokenManager, l as TokenManagerOptions, m as TokenResponse, n as TokenStorage, o as Turn, p as TurnAdvisory, q as TurnBlock, r as TurnError, s as createConversation, t as createLainternClient, u as createTokenManager, v as decodeJwtExpiryMs, w as isNavigable, x as resolveCitation, y as segmentAnswer } from './citations-BR_LD7jF.js';
3
3
 
4
4
  /**
5
5
  * Every failure the SDK surfaces, with the API's own error code attached.
package/dist/index.js CHANGED
@@ -1,5 +1,5 @@
1
- import { errorFromResponse, LainternError } from './chunk-WMKNNVMS.js';
2
- export { LainternError, createConversation } from './chunk-WMKNNVMS.js';
1
+ import { errorFromResponse, LainternError } from './chunk-OSMAH2QV.js';
2
+ export { LainternError, createConversation, isNavigable, resolveCitation, segmentAnswer } from './chunk-OSMAH2QV.js';
3
3
 
4
4
  // src/sse.ts
5
5
  var DONE = "[DONE]";
@@ -238,6 +238,49 @@ function applyFailure(patchLast, failure) {
238
238
  }));
239
239
  }
240
240
 
241
+ // src/citations.ts
242
+ var SOURCE_INDEX_RE = /^source(\d+)$/;
243
+ function sourceIndex(source) {
244
+ const match = source.content_id ? SOURCE_INDEX_RE.exec(source.content_id) : null;
245
+ return match ? Number.parseInt(match[1], 10) : Number.POSITIVE_INFINITY;
246
+ }
247
+ function fromSearchResult(source) {
248
+ return {
249
+ source_id: source.content_id || source.url,
250
+ title: source.title,
251
+ url: source.url,
252
+ content_type: source.content_type,
253
+ ...source.section ? { section: source.section } : {}
254
+ };
255
+ }
256
+ function resolveCitation(index, turn) {
257
+ const exact = turn.citations.get(index);
258
+ if (exact) return exact;
259
+ if (index < 1 || turn.sources.length === 0) return null;
260
+ const ordered = [...turn.sources].sort((a, b) => sourceIndex(a) - sourceIndex(b));
261
+ const fallback = ordered[index - 1];
262
+ return fallback ? fromSearchResult(fallback) : null;
263
+ }
264
+ function isNavigable(source) {
265
+ return typeof source.url === "string" && source.url.trim().length > 0;
266
+ }
267
+ var CITATION_PATTERN = /\[(\d+)\]/g;
268
+ function segmentAnswer(content, turn) {
269
+ const segments = [];
270
+ let lastIndex = 0;
271
+ for (const match of content.matchAll(CITATION_PATTERN)) {
272
+ const at = match.index ?? 0;
273
+ if (at > lastIndex) segments.push({ type: "text", text: content.slice(lastIndex, at) });
274
+ const index = Number.parseInt(match[1], 10);
275
+ segments.push({ type: "citation", index, source: resolveCitation(index, turn) });
276
+ lastIndex = at + match[0].length;
277
+ }
278
+ if (lastIndex < content.length) {
279
+ segments.push({ type: "text", text: content.slice(lastIndex) });
280
+ }
281
+ return segments;
282
+ }
283
+
241
284
  // src/react/index.ts
242
285
  function useLainternChat(client, options = {}) {
243
286
  const optionsRef = react.useRef(options);
@@ -263,6 +306,9 @@ function useLainternChat(client, options = {}) {
263
306
  return { ...state, send, abort, reset, setFeedback, conversation };
264
307
  }
265
308
 
309
+ exports.isNavigable = isNavigable;
310
+ exports.resolveCitation = resolveCitation;
311
+ exports.segmentAnswer = segmentAnswer;
266
312
  exports.useLainternChat = useLainternChat;
267
313
  //# sourceMappingURL=index.cjs.map
268
314
  //# sourceMappingURL=index.cjs.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/errors.ts","../../src/conversation.ts","../../src/react/index.ts"],"names":["useRef","useMemo","useSyncExternalStore","useCallback"],"mappings":"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","file":"index.cjs","sourcesContent":["/**\n * Every failure the SDK surfaces, with the API's own error code attached.\n *\n * Callers switch on `code` rather than parsing messages: the server's copy is\n * user-facing prose that may change, while the code is part of the contract.\n */\nexport class LainternError extends Error {\n readonly code: string\n readonly status: number | null\n /** Set on RATE_LIMIT_EXCEEDED. */\n readonly rateLimitScope?: \"user\" | \"agent\"\n readonly rateLimitWindow?: \"minute\" | \"hour\" | \"day\"\n /** Set on MCP_UNAVAILABLE: which dependency failed, and how. */\n readonly reason?: string\n\n constructor(\n code: string,\n message: string,\n options: {\n status?: number | null\n rateLimitScope?: \"user\" | \"agent\"\n rateLimitWindow?: \"minute\" | \"hour\" | \"day\"\n reason?: string\n cause?: unknown\n } = {},\n ) {\n super(message, options.cause !== undefined ? { cause: options.cause } : undefined)\n this.name = \"LainternError\"\n this.code = code\n this.status = options.status ?? null\n if (options.rateLimitScope) this.rateLimitScope = options.rateLimitScope\n if (options.rateLimitWindow) this.rateLimitWindow = options.rateLimitWindow\n if (options.reason) this.reason = options.reason\n }\n\n /**\n * True when retrying later could plausibly succeed: a rate limit, or a\n * dependency the server could not reach. An expired session is NOT retryable\n * — the host has to mint a new token first.\n */\n get isRetryable(): boolean {\n return this.code === \"RATE_LIMIT_EXCEEDED\" || this.reason === \"transport\"\n }\n}\n\nfunction pick<T extends string>(value: unknown, allowed: readonly T[]): T | undefined {\n return typeof value === \"string\" && (allowed as readonly string[]).includes(value)\n ? (value as T)\n : undefined\n}\n\n/** Turn a non-2xx API response into a `LainternError`. Never throws itself. */\nexport async function errorFromResponse(response: Response): Promise<LainternError> {\n let code = `HTTP_${response.status}`\n let message = \"An unexpected error occurred.\"\n let rateLimitScope: \"user\" | \"agent\" | undefined\n let rateLimitWindow: \"minute\" | \"hour\" | \"day\" | undefined\n let reason: string | undefined\n\n try {\n const body = (await response.json()) as {\n error?: {\n code?: string\n message?: string\n scope?: unknown\n window?: unknown\n reason?: unknown\n }\n message?: string\n }\n if (body?.error?.code) {\n code = body.error.code\n message = body.error.message ?? message\n rateLimitScope = pick(body.error.scope, [\"user\", \"agent\"] as const)\n rateLimitWindow = pick(body.error.window, [\"minute\", \"hour\", \"day\"] as const)\n if (typeof body.error.reason === \"string\") reason = body.error.reason\n } else if (typeof body?.message === \"string\") {\n message = body.message\n }\n } catch {\n // Not JSON — keep the HTTP-derived defaults.\n }\n\n return new LainternError(code, message, {\n status: response.status,\n rateLimitScope,\n rateLimitWindow,\n reason,\n })\n}\n","import type {\n CitationSource,\n ExternalSourceType,\n SearchResult,\n StepEvent,\n} from \"@laintern-headless/types\"\nimport type { LainternClient } from \"./client.js\"\nimport { LainternError } from \"./errors.js\"\n\n/** Soft advisory alongside an answer — the agent flagged the topic, but answered. */\nexport interface TurnAdvisory {\n redirect: string | null\n message: string\n}\n\n/** A guardrail refused the turn. The agent produced no answer. */\nexport interface TurnBlock {\n /** Which guardrail fired, when the server names one. */\n slug: string | null\n /** Copy meant for the end user, written by the guardrail's configuration. */\n message: string\n}\n\nexport interface TurnError {\n code: string\n message: string\n rateLimitScope?: \"user\" | \"agent\"\n rateLimitWindow?: \"minute\" | \"hour\" | \"day\"\n}\n\nexport interface Turn {\n id: string\n userMessage: string\n userImages?: string[]\n assistantContent: string\n /** Citation number (the `[1]` in the text) → the source it points at. */\n citations: Map<number, CitationSource>\n /** Everything the agent cited or consulted, in the order the server ranked it. */\n sources: SearchResult[]\n /** Sidebar suggestions per content type — not necessarily cited. */\n externalSources: Partial<Record<ExternalSourceType, SearchResult[]>>\n mode: string | null\n steps: StepEvent[]\n isStreaming: boolean\n error?: TurnError\n blocked?: TurnBlock\n advisory?: TurnAdvisory\n messageId?: string\n feedback?: \"positive\" | \"negative\" | null\n}\n\nexport interface ConversationState {\n turns: Turn[]\n sessionId: string | null\n isStreaming: boolean\n}\n\nexport interface SendOptions {\n images?: string[]\n forceTool?: string\n}\n\nexport interface Conversation {\n getState(): ConversationState\n /** Subscribe to state changes. Returns an unsubscribe function. */\n subscribe(listener: (state: ConversationState) => void): () => void\n /** Send a message and stream the answer into state. Never throws. */\n send(message: string, options?: SendOptions): Promise<void>\n /** Stop the current stream. The partial answer stays in state. */\n abort(): void\n /** Drop all turns and start a new server-side session on the next send. */\n reset(): void\n setFeedback(turnId: string, feedback: \"positive\" | \"negative\"): Promise<void>\n}\n\nexport interface ConversationOptions {\n /** Resume an existing server-side session. */\n sessionId?: string | null\n /** Restore turns from your own storage. */\n initialTurns?: Turn[]\n /** Called whenever the session id changes, so you can persist it. */\n onSessionId?: (sessionId: string) => void\n}\n\nfunction newId(): string {\n const c = globalThis.crypto\n if (c && typeof c.randomUUID === \"function\") return c.randomUUID()\n return `turn-${Date.now()}-${Math.random().toString(36).slice(2, 10)}`\n}\n\nfunction createTurn(message: string, images?: string[]): Turn {\n return {\n id: newId(),\n userMessage: message,\n ...(images?.length ? { userImages: images } : {}),\n assistantContent: \"\",\n citations: new Map(),\n sources: [],\n externalSources: {},\n mode: null,\n steps: [],\n isStreaming: true,\n }\n}\n\n/**\n * Stateful conversation on top of {@link LainternClient}.\n *\n * Holds the turn list, applies each stream event to the turn in flight, and\n * notifies subscribers. Framework-agnostic on purpose: the React binding in\n * `@laintern/chat-sdk/react` is a thin `useSyncExternalStore` wrapper, and any\n * other UI layer can subscribe the same way.\n *\n * `send()` never throws. A failed turn is part of the conversation — it lands\n * on `turn.error` (or `turn.blocked` for a guardrail) so the UI can render it\n * in place, which is what you want in a chat transcript.\n */\nexport function createConversation(\n client: LainternClient,\n options: ConversationOptions = {},\n): Conversation {\n let state: ConversationState = {\n turns: options.initialTurns ?? [],\n sessionId: options.sessionId ?? null,\n isStreaming: false,\n }\n const listeners = new Set<(state: ConversationState) => void>()\n let controller: AbortController | null = null\n\n function emit(next: ConversationState): void {\n state = next\n for (const listener of listeners) listener(state)\n }\n\n /** Apply a change to the turn currently streaming (always the last one). */\n function patchLast(patch: (turn: Turn) => Turn): void {\n const lastIndex = state.turns.length - 1\n if (lastIndex < 0) return\n const turns = [...state.turns]\n turns[lastIndex] = patch(turns[lastIndex])\n emit({ ...state, turns })\n }\n\n return {\n getState: () => state,\n\n subscribe(listener) {\n listeners.add(listener)\n return () => listeners.delete(listener)\n },\n\n abort() {\n controller?.abort()\n },\n\n reset() {\n controller?.abort()\n emit({ turns: [], sessionId: null, isStreaming: false })\n },\n\n async send(message, sendOptions = {}) {\n controller?.abort()\n controller = new AbortController()\n const signal = controller.signal\n\n emit({\n ...state,\n turns: [...state.turns, createTurn(message, sendOptions.images)],\n isStreaming: true,\n })\n\n try {\n const stream = client.sendMessage(message, {\n sessionId: state.sessionId,\n ...(sendOptions.images ? { images: sendOptions.images } : {}),\n ...(sendOptions.forceTool ? { forceTool: sendOptions.forceTool } : {}),\n signal,\n })\n\n for await (const event of stream) {\n if (signal.aborted) break\n\n switch (event.type) {\n case \"session\":\n emit({ ...state, sessionId: event.session_id })\n options.onSessionId?.(event.session_id)\n break\n case \"mode\":\n patchLast((turn) => ({ ...turn, mode: event.mode }))\n break\n case \"step\":\n patchLast((turn) => ({ ...turn, steps: mergeStep(turn.steps, event.step) }))\n break\n case \"delta\":\n patchLast((turn) => ({\n ...turn,\n assistantContent: turn.assistantContent + event.content,\n }))\n break\n case \"citation\":\n patchLast((turn) => {\n const citations = new Map(turn.citations)\n citations.set(event.index, event.source)\n return { ...turn, citations }\n })\n break\n case \"sources\":\n patchLast((turn) => ({ ...turn, sources: event.items }))\n break\n case \"external_sources\":\n patchLast((turn) => ({\n ...turn,\n externalSources: {\n ...turn.externalSources,\n [event.source_type]: event.items,\n },\n }))\n break\n case \"advisory\":\n patchLast((turn) => ({\n ...turn,\n advisory: { redirect: event.redirect ?? null, message: event.message },\n }))\n break\n case \"done\":\n patchLast((turn) => ({\n ...turn,\n isStreaming: false,\n ...(event.message_id ? { messageId: event.message_id } : {}),\n }))\n emit({ ...state, isStreaming: false })\n break\n case \"error\":\n applyFailure(patchLast, {\n code: event.code,\n message: event.message,\n guardrailSlug: event.guardrail_slug ?? null,\n })\n break\n }\n }\n } catch (err) {\n if ((err as Error)?.name === \"AbortError\") {\n patchLast((turn) => ({ ...turn, isStreaming: false }))\n } else if (err instanceof LainternError) {\n applyFailure(patchLast, {\n code: err.code,\n message: err.message,\n guardrailSlug: null,\n ...(err.rateLimitScope ? { rateLimitScope: err.rateLimitScope } : {}),\n ...(err.rateLimitWindow ? { rateLimitWindow: err.rateLimitWindow } : {}),\n })\n } else {\n applyFailure(patchLast, {\n code: \"NETWORK_ERROR\",\n message: err instanceof Error ? err.message : \"An unknown error occurred.\",\n guardrailSlug: null,\n })\n }\n } finally {\n // A turn always stops streaming, however it ended — including an abort\n // that broke the loop without throwing. Leaving the flag set strands a\n // typing indicator on a turn that will never produce another token.\n const last = state.turns.at(-1)\n if (last?.isStreaming) patchLast((turn) => ({ ...turn, isStreaming: false }))\n if (state.isStreaming) emit({ ...state, isStreaming: false })\n controller = null\n }\n },\n\n async setFeedback(turnId, feedback) {\n const turn = state.turns.find((t) => t.id === turnId)\n if (!turn?.messageId) {\n throw new LainternError(\n \"FEEDBACK_UNAVAILABLE\",\n \"This turn has no message id yet — feedback is only possible once a turn completes.\",\n )\n }\n await client.submitFeedback(turn.messageId, feedback)\n emit({\n ...state,\n turns: state.turns.map((t) => (t.id === turnId ? { ...t, feedback } : t)),\n })\n },\n }\n}\n\n/**\n * A completed step replaces its own \"started\" entry rather than appending, so\n * the UI shows one row per tool call that fills in, not two.\n */\nfunction mergeStep(steps: StepEvent[], step: StepEvent): StepEvent[] {\n if (step.status !== \"completed\") return [...steps, step]\n const startedIndex = steps.findIndex((s) => s.tool === step.tool && s.status === \"started\")\n if (startedIndex === -1) return [...steps, step]\n const next = [...steps]\n next[startedIndex] = step\n return next\n}\n\n/**\n * A failed or blocked turn.\n *\n * A guardrail block discards whatever streamed before it fired: the partial\n * answer is exactly the content the guardrail decided the user should not see,\n * and a hard block always wins over a soft advisory that arrived a moment\n * earlier.\n */\nfunction applyFailure(\n patchLast: (patch: (turn: Turn) => Turn) => void,\n failure: {\n code: string\n message: string\n guardrailSlug: string | null\n rateLimitScope?: \"user\" | \"agent\"\n rateLimitWindow?: \"minute\" | \"hour\" | \"day\"\n },\n): void {\n if (failure.code === \"GUARDRAIL_TRIGGERED\") {\n patchLast((turn) => ({\n ...turn,\n isStreaming: false,\n assistantContent: \"\",\n citations: new Map(),\n sources: [],\n externalSources: {},\n steps: [],\n mode: null,\n advisory: undefined,\n blocked: { slug: failure.guardrailSlug, message: failure.message },\n }))\n return\n }\n\n patchLast((turn) => ({\n ...turn,\n isStreaming: false,\n error: {\n code: failure.code,\n message: failure.message,\n ...(failure.rateLimitScope ? { rateLimitScope: failure.rateLimitScope } : {}),\n ...(failure.rateLimitWindow ? { rateLimitWindow: failure.rateLimitWindow } : {}),\n },\n }))\n}\n","import { useCallback, useMemo, useRef, useSyncExternalStore } from \"react\"\nimport { createConversation } from \"../conversation.js\"\nimport type {\n Conversation,\n ConversationOptions,\n ConversationState,\n SendOptions,\n} from \"../conversation.js\"\nimport type { LainternClient } from \"../client.js\"\n\nexport interface UseLainternChatResult extends ConversationState {\n send: (message: string, options?: SendOptions) => Promise<void>\n abort: () => void\n reset: () => void\n setFeedback: (turnId: string, feedback: \"positive\" | \"negative\") => Promise<void>\n /** The underlying store, if you need it outside React. */\n conversation: Conversation\n}\n\n/**\n * React binding for {@link createConversation}.\n *\n * The conversation lives outside React and is read through\n * `useSyncExternalStore`, so a streaming answer re-renders only the components\n * that read it — and the transcript survives a parent re-render.\n *\n * The client and options are captured on first render: pass a client created\n * once (module scope, or a `useMemo` in your provider), not a fresh one per\n * render.\n */\nexport function useLainternChat(\n client: LainternClient,\n options: ConversationOptions = {},\n): UseLainternChatResult {\n const optionsRef = useRef(options)\n const conversation = useMemo(\n () => createConversation(client, optionsRef.current),\n [client],\n )\n\n const state = useSyncExternalStore(\n useCallback((listener) => conversation.subscribe(listener), [conversation]),\n useCallback(() => conversation.getState(), [conversation]),\n useCallback(() => conversation.getState(), [conversation]),\n )\n\n const send = useCallback(\n (message: string, sendOptions?: SendOptions) => conversation.send(message, sendOptions),\n [conversation],\n )\n const abort = useCallback(() => conversation.abort(), [conversation])\n const reset = useCallback(() => conversation.reset(), [conversation])\n const setFeedback = useCallback(\n (turnId: string, feedback: \"positive\" | \"negative\") =>\n conversation.setFeedback(turnId, feedback),\n [conversation],\n )\n\n return { ...state, send, abort, reset, setFeedback, conversation }\n}\n\nexport type { Conversation, ConversationOptions, ConversationState, SendOptions } from \"../conversation.js\"\nexport type { Turn, TurnAdvisory, TurnBlock, TurnError } from \"../conversation.js\"\n"]}
1
+ {"version":3,"sources":["../../src/errors.ts","../../src/conversation.ts","../../src/citations.ts","../../src/react/index.ts"],"names":["useRef","useMemo","useSyncExternalStore","useCallback"],"mappings":"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","file":"index.cjs","sourcesContent":["/**\n * Every failure the SDK surfaces, with the API's own error code attached.\n *\n * Callers switch on `code` rather than parsing messages: the server's copy is\n * user-facing prose that may change, while the code is part of the contract.\n */\nexport class LainternError extends Error {\n readonly code: string\n readonly status: number | null\n /** Set on RATE_LIMIT_EXCEEDED. */\n readonly rateLimitScope?: \"user\" | \"agent\"\n readonly rateLimitWindow?: \"minute\" | \"hour\" | \"day\"\n /** Set on MCP_UNAVAILABLE: which dependency failed, and how. */\n readonly reason?: string\n\n constructor(\n code: string,\n message: string,\n options: {\n status?: number | null\n rateLimitScope?: \"user\" | \"agent\"\n rateLimitWindow?: \"minute\" | \"hour\" | \"day\"\n reason?: string\n cause?: unknown\n } = {},\n ) {\n super(message, options.cause !== undefined ? { cause: options.cause } : undefined)\n this.name = \"LainternError\"\n this.code = code\n this.status = options.status ?? null\n if (options.rateLimitScope) this.rateLimitScope = options.rateLimitScope\n if (options.rateLimitWindow) this.rateLimitWindow = options.rateLimitWindow\n if (options.reason) this.reason = options.reason\n }\n\n /**\n * True when retrying later could plausibly succeed: a rate limit, or a\n * dependency the server could not reach. An expired session is NOT retryable\n * — the host has to mint a new token first.\n */\n get isRetryable(): boolean {\n return this.code === \"RATE_LIMIT_EXCEEDED\" || this.reason === \"transport\"\n }\n}\n\nfunction pick<T extends string>(value: unknown, allowed: readonly T[]): T | undefined {\n return typeof value === \"string\" && (allowed as readonly string[]).includes(value)\n ? (value as T)\n : undefined\n}\n\n/** Turn a non-2xx API response into a `LainternError`. Never throws itself. */\nexport async function errorFromResponse(response: Response): Promise<LainternError> {\n let code = `HTTP_${response.status}`\n let message = \"An unexpected error occurred.\"\n let rateLimitScope: \"user\" | \"agent\" | undefined\n let rateLimitWindow: \"minute\" | \"hour\" | \"day\" | undefined\n let reason: string | undefined\n\n try {\n const body = (await response.json()) as {\n error?: {\n code?: string\n message?: string\n scope?: unknown\n window?: unknown\n reason?: unknown\n }\n message?: string\n }\n if (body?.error?.code) {\n code = body.error.code\n message = body.error.message ?? message\n rateLimitScope = pick(body.error.scope, [\"user\", \"agent\"] as const)\n rateLimitWindow = pick(body.error.window, [\"minute\", \"hour\", \"day\"] as const)\n if (typeof body.error.reason === \"string\") reason = body.error.reason\n } else if (typeof body?.message === \"string\") {\n message = body.message\n }\n } catch {\n // Not JSON — keep the HTTP-derived defaults.\n }\n\n return new LainternError(code, message, {\n status: response.status,\n rateLimitScope,\n rateLimitWindow,\n reason,\n })\n}\n","import type {\n CitationSource,\n ExternalSourceType,\n SearchResult,\n StepEvent,\n} from \"@laintern-headless/types\"\nimport type { LainternClient } from \"./client.js\"\nimport { LainternError } from \"./errors.js\"\n\n/** Soft advisory alongside an answer — the agent flagged the topic, but answered. */\nexport interface TurnAdvisory {\n redirect: string | null\n message: string\n}\n\n/** A guardrail refused the turn. The agent produced no answer. */\nexport interface TurnBlock {\n /** Which guardrail fired, when the server names one. */\n slug: string | null\n /** Copy meant for the end user, written by the guardrail's configuration. */\n message: string\n}\n\nexport interface TurnError {\n code: string\n message: string\n rateLimitScope?: \"user\" | \"agent\"\n rateLimitWindow?: \"minute\" | \"hour\" | \"day\"\n}\n\nexport interface Turn {\n id: string\n userMessage: string\n userImages?: string[]\n assistantContent: string\n /** Citation number (the `[1]` in the text) → the source it points at. */\n citations: Map<number, CitationSource>\n /** Everything the agent cited or consulted, in the order the server ranked it. */\n sources: SearchResult[]\n /** Sidebar suggestions per content type — not necessarily cited. */\n externalSources: Partial<Record<ExternalSourceType, SearchResult[]>>\n mode: string | null\n steps: StepEvent[]\n isStreaming: boolean\n error?: TurnError\n blocked?: TurnBlock\n advisory?: TurnAdvisory\n messageId?: string\n feedback?: \"positive\" | \"negative\" | null\n}\n\nexport interface ConversationState {\n turns: Turn[]\n sessionId: string | null\n isStreaming: boolean\n}\n\nexport interface SendOptions {\n images?: string[]\n forceTool?: string\n}\n\nexport interface Conversation {\n getState(): ConversationState\n /** Subscribe to state changes. Returns an unsubscribe function. */\n subscribe(listener: (state: ConversationState) => void): () => void\n /** Send a message and stream the answer into state. Never throws. */\n send(message: string, options?: SendOptions): Promise<void>\n /** Stop the current stream. The partial answer stays in state. */\n abort(): void\n /** Drop all turns and start a new server-side session on the next send. */\n reset(): void\n setFeedback(turnId: string, feedback: \"positive\" | \"negative\"): Promise<void>\n}\n\nexport interface ConversationOptions {\n /** Resume an existing server-side session. */\n sessionId?: string | null\n /** Restore turns from your own storage. */\n initialTurns?: Turn[]\n /** Called whenever the session id changes, so you can persist it. */\n onSessionId?: (sessionId: string) => void\n}\n\nfunction newId(): string {\n const c = globalThis.crypto\n if (c && typeof c.randomUUID === \"function\") return c.randomUUID()\n return `turn-${Date.now()}-${Math.random().toString(36).slice(2, 10)}`\n}\n\nfunction createTurn(message: string, images?: string[]): Turn {\n return {\n id: newId(),\n userMessage: message,\n ...(images?.length ? { userImages: images } : {}),\n assistantContent: \"\",\n citations: new Map(),\n sources: [],\n externalSources: {},\n mode: null,\n steps: [],\n isStreaming: true,\n }\n}\n\n/**\n * Stateful conversation on top of {@link LainternClient}.\n *\n * Holds the turn list, applies each stream event to the turn in flight, and\n * notifies subscribers. Framework-agnostic on purpose: the React binding in\n * `@laintern/chat-sdk/react` is a thin `useSyncExternalStore` wrapper, and any\n * other UI layer can subscribe the same way.\n *\n * `send()` never throws. A failed turn is part of the conversation — it lands\n * on `turn.error` (or `turn.blocked` for a guardrail) so the UI can render it\n * in place, which is what you want in a chat transcript.\n */\nexport function createConversation(\n client: LainternClient,\n options: ConversationOptions = {},\n): Conversation {\n let state: ConversationState = {\n turns: options.initialTurns ?? [],\n sessionId: options.sessionId ?? null,\n isStreaming: false,\n }\n const listeners = new Set<(state: ConversationState) => void>()\n let controller: AbortController | null = null\n\n function emit(next: ConversationState): void {\n state = next\n for (const listener of listeners) listener(state)\n }\n\n /** Apply a change to the turn currently streaming (always the last one). */\n function patchLast(patch: (turn: Turn) => Turn): void {\n const lastIndex = state.turns.length - 1\n if (lastIndex < 0) return\n const turns = [...state.turns]\n turns[lastIndex] = patch(turns[lastIndex])\n emit({ ...state, turns })\n }\n\n return {\n getState: () => state,\n\n subscribe(listener) {\n listeners.add(listener)\n return () => listeners.delete(listener)\n },\n\n abort() {\n controller?.abort()\n },\n\n reset() {\n controller?.abort()\n emit({ turns: [], sessionId: null, isStreaming: false })\n },\n\n async send(message, sendOptions = {}) {\n controller?.abort()\n controller = new AbortController()\n const signal = controller.signal\n\n emit({\n ...state,\n turns: [...state.turns, createTurn(message, sendOptions.images)],\n isStreaming: true,\n })\n\n try {\n const stream = client.sendMessage(message, {\n sessionId: state.sessionId,\n ...(sendOptions.images ? { images: sendOptions.images } : {}),\n ...(sendOptions.forceTool ? { forceTool: sendOptions.forceTool } : {}),\n signal,\n })\n\n for await (const event of stream) {\n if (signal.aborted) break\n\n switch (event.type) {\n case \"session\":\n emit({ ...state, sessionId: event.session_id })\n options.onSessionId?.(event.session_id)\n break\n case \"mode\":\n patchLast((turn) => ({ ...turn, mode: event.mode }))\n break\n case \"step\":\n patchLast((turn) => ({ ...turn, steps: mergeStep(turn.steps, event.step) }))\n break\n case \"delta\":\n patchLast((turn) => ({\n ...turn,\n assistantContent: turn.assistantContent + event.content,\n }))\n break\n case \"citation\":\n patchLast((turn) => {\n const citations = new Map(turn.citations)\n citations.set(event.index, event.source)\n return { ...turn, citations }\n })\n break\n case \"sources\":\n patchLast((turn) => ({ ...turn, sources: event.items }))\n break\n case \"external_sources\":\n patchLast((turn) => ({\n ...turn,\n externalSources: {\n ...turn.externalSources,\n [event.source_type]: event.items,\n },\n }))\n break\n case \"advisory\":\n patchLast((turn) => ({\n ...turn,\n advisory: { redirect: event.redirect ?? null, message: event.message },\n }))\n break\n case \"done\":\n patchLast((turn) => ({\n ...turn,\n isStreaming: false,\n ...(event.message_id ? { messageId: event.message_id } : {}),\n }))\n emit({ ...state, isStreaming: false })\n break\n case \"error\":\n applyFailure(patchLast, {\n code: event.code,\n message: event.message,\n guardrailSlug: event.guardrail_slug ?? null,\n })\n break\n }\n }\n } catch (err) {\n if ((err as Error)?.name === \"AbortError\") {\n patchLast((turn) => ({ ...turn, isStreaming: false }))\n } else if (err instanceof LainternError) {\n applyFailure(patchLast, {\n code: err.code,\n message: err.message,\n guardrailSlug: null,\n ...(err.rateLimitScope ? { rateLimitScope: err.rateLimitScope } : {}),\n ...(err.rateLimitWindow ? { rateLimitWindow: err.rateLimitWindow } : {}),\n })\n } else {\n applyFailure(patchLast, {\n code: \"NETWORK_ERROR\",\n message: err instanceof Error ? err.message : \"An unknown error occurred.\",\n guardrailSlug: null,\n })\n }\n } finally {\n // A turn always stops streaming, however it ended — including an abort\n // that broke the loop without throwing. Leaving the flag set strands a\n // typing indicator on a turn that will never produce another token.\n const last = state.turns.at(-1)\n if (last?.isStreaming) patchLast((turn) => ({ ...turn, isStreaming: false }))\n if (state.isStreaming) emit({ ...state, isStreaming: false })\n controller = null\n }\n },\n\n async setFeedback(turnId, feedback) {\n const turn = state.turns.find((t) => t.id === turnId)\n if (!turn?.messageId) {\n throw new LainternError(\n \"FEEDBACK_UNAVAILABLE\",\n \"This turn has no message id yet — feedback is only possible once a turn completes.\",\n )\n }\n await client.submitFeedback(turn.messageId, feedback)\n emit({\n ...state,\n turns: state.turns.map((t) => (t.id === turnId ? { ...t, feedback } : t)),\n })\n },\n }\n}\n\n/**\n * A completed step replaces its own \"started\" entry rather than appending, so\n * the UI shows one row per tool call that fills in, not two.\n */\nfunction mergeStep(steps: StepEvent[], step: StepEvent): StepEvent[] {\n if (step.status !== \"completed\") return [...steps, step]\n const startedIndex = steps.findIndex((s) => s.tool === step.tool && s.status === \"started\")\n if (startedIndex === -1) return [...steps, step]\n const next = [...steps]\n next[startedIndex] = step\n return next\n}\n\n/**\n * A failed or blocked turn.\n *\n * A guardrail block discards whatever streamed before it fired: the partial\n * answer is exactly the content the guardrail decided the user should not see,\n * and a hard block always wins over a soft advisory that arrived a moment\n * earlier.\n */\nfunction applyFailure(\n patchLast: (patch: (turn: Turn) => Turn) => void,\n failure: {\n code: string\n message: string\n guardrailSlug: string | null\n rateLimitScope?: \"user\" | \"agent\"\n rateLimitWindow?: \"minute\" | \"hour\" | \"day\"\n },\n): void {\n if (failure.code === \"GUARDRAIL_TRIGGERED\") {\n patchLast((turn) => ({\n ...turn,\n isStreaming: false,\n assistantContent: \"\",\n citations: new Map(),\n sources: [],\n externalSources: {},\n steps: [],\n mode: null,\n advisory: undefined,\n blocked: { slug: failure.guardrailSlug, message: failure.message },\n }))\n return\n }\n\n patchLast((turn) => ({\n ...turn,\n isStreaming: false,\n error: {\n code: failure.code,\n message: failure.message,\n ...(failure.rateLimitScope ? { rateLimitScope: failure.rateLimitScope } : {}),\n ...(failure.rateLimitWindow ? { rateLimitWindow: failure.rateLimitWindow } : {}),\n },\n }))\n}\n","import type { CitationSource, SearchResult } from \"@laintern-headless/types\"\nimport type { Turn } from \"./conversation.js\"\n\n/** Source ids the server hands out are `source0`, `source1`, … in call order. */\nconst SOURCE_INDEX_RE = /^source(\\d+)$/\n\nfunction sourceIndex(source: SearchResult): number {\n const match = source.content_id ? SOURCE_INDEX_RE.exec(source.content_id) : null\n return match ? Number.parseInt(match[1], 10) : Number.POSITIVE_INFINITY\n}\n\nfunction fromSearchResult(source: SearchResult): CitationSource {\n return {\n source_id: source.content_id || source.url,\n title: source.title,\n url: source.url,\n content_type: source.content_type,\n ...(source.section ? { section: source.section } : {}),\n }\n}\n\n/**\n * Resolve a `[n]` marker in the answer text to the source it points at.\n *\n * The answer streams token by token, so a marker can render before its\n * `citation` event arrives. The fallback covers that gap by position: sources\n * are numbered in the order the agent collected them, so the n-th source is\n * the one the n-th marker means. Returns null when neither is available yet —\n * render the bare `[n]` and let it resolve on the next frame.\n */\nexport function resolveCitation(index: number, turn: Pick<Turn, \"citations\" | \"sources\">): CitationSource | null {\n const exact = turn.citations.get(index)\n if (exact) return exact\n if (index < 1 || turn.sources.length === 0) return null\n\n const ordered = [...turn.sources].sort((a, b) => sourceIndex(a) - sourceIndex(b))\n const fallback = ordered[index - 1]\n return fallback ? fromSearchResult(fallback) : null\n}\n\n/**\n * True when a citation has nowhere to link to — a knowledge-base passage\n * lives in the client's own document store, not on the public web. Render\n * those as text (title, plus `section` when present), never as a dead link.\n */\nexport function isNavigable(source: CitationSource): boolean {\n return typeof source.url === \"string\" && source.url.trim().length > 0\n}\n\n/** Splits the answer into text and citation markers, in order. */\nexport type AnswerSegment =\n | { type: \"text\"; text: string }\n | { type: \"citation\"; index: number; source: CitationSource | null }\n\nconst CITATION_PATTERN = /\\[(\\d+)\\]/g\n\n/**\n * Walk the answer text and hand back its segments, with each `[n]` already\n * resolved. Lets a UI render citations as components without writing its own\n * parser — and without turning the markdown into HTML first.\n */\nexport function segmentAnswer(\n content: string,\n turn: Pick<Turn, \"citations\" | \"sources\">,\n): AnswerSegment[] {\n const segments: AnswerSegment[] = []\n let lastIndex = 0\n\n for (const match of content.matchAll(CITATION_PATTERN)) {\n const at = match.index ?? 0\n if (at > lastIndex) segments.push({ type: \"text\", text: content.slice(lastIndex, at) })\n const index = Number.parseInt(match[1], 10)\n segments.push({ type: \"citation\", index, source: resolveCitation(index, turn) })\n lastIndex = at + match[0].length\n }\n if (lastIndex < content.length) {\n segments.push({ type: \"text\", text: content.slice(lastIndex) })\n }\n return segments\n}\n","import { useCallback, useMemo, useRef, useSyncExternalStore } from \"react\"\nimport { createConversation } from \"../conversation.js\"\nimport type {\n Conversation,\n ConversationOptions,\n ConversationState,\n SendOptions,\n} from \"../conversation.js\"\nimport type { LainternClient } from \"../client.js\"\n\nexport interface UseLainternChatResult extends ConversationState {\n send: (message: string, options?: SendOptions) => Promise<void>\n abort: () => void\n reset: () => void\n setFeedback: (turnId: string, feedback: \"positive\" | \"negative\") => Promise<void>\n /** The underlying store, if you need it outside React. */\n conversation: Conversation\n}\n\n/**\n * React binding for {@link createConversation}.\n *\n * The conversation lives outside React and is read through\n * `useSyncExternalStore`, so a streaming answer re-renders only the components\n * that read it — and the transcript survives a parent re-render.\n *\n * The client and options are captured on first render: pass a client created\n * once (module scope, or a `useMemo` in your provider), not a fresh one per\n * render.\n */\nexport function useLainternChat(\n client: LainternClient,\n options: ConversationOptions = {},\n): UseLainternChatResult {\n const optionsRef = useRef(options)\n const conversation = useMemo(\n () => createConversation(client, optionsRef.current),\n [client],\n )\n\n const state = useSyncExternalStore(\n useCallback((listener) => conversation.subscribe(listener), [conversation]),\n useCallback(() => conversation.getState(), [conversation]),\n useCallback(() => conversation.getState(), [conversation]),\n )\n\n const send = useCallback(\n (message: string, sendOptions?: SendOptions) => conversation.send(message, sendOptions),\n [conversation],\n )\n const abort = useCallback(() => conversation.abort(), [conversation])\n const reset = useCallback(() => conversation.reset(), [conversation])\n const setFeedback = useCallback(\n (turnId: string, feedback: \"positive\" | \"negative\") =>\n conversation.setFeedback(turnId, feedback),\n [conversation],\n )\n\n return { ...state, send, abort, reset, setFeedback, conversation }\n}\n\nexport type { Conversation, ConversationOptions, ConversationState, SendOptions } from \"../conversation.js\"\nexport type { Turn, TurnAdvisory, TurnBlock, TurnError } from \"../conversation.js\"\nexport { isNavigable, resolveCitation, segmentAnswer } from \"../citations.js\"\nexport type { AnswerSegment } from \"../citations.js\"\n"]}
@@ -1,5 +1,5 @@
1
- import { f as ConversationState, i as SendOptions, d as Conversation, L as LainternClient, e as ConversationOptions } from '../conversation-7VzVDT95.cjs';
2
- export { n as Turn, o as TurnAdvisory, p as TurnBlock, q as TurnError } from '../conversation-7VzVDT95.cjs';
1
+ import { g as ConversationState, j as SendOptions, e as Conversation, L as LainternClient, f as ConversationOptions } from '../citations-BR_LD7jF.cjs';
2
+ export { a as AnswerSegment, o as Turn, p as TurnAdvisory, q as TurnBlock, r as TurnError, w as isNavigable, x as resolveCitation, y as segmentAnswer } from '../citations-BR_LD7jF.cjs';
3
3
 
4
4
  interface UseLainternChatResult extends ConversationState {
5
5
  send: (message: string, options?: SendOptions) => Promise<void>;
@@ -1,5 +1,5 @@
1
- import { f as ConversationState, i as SendOptions, d as Conversation, L as LainternClient, e as ConversationOptions } from '../conversation-7VzVDT95.js';
2
- export { n as Turn, o as TurnAdvisory, p as TurnBlock, q as TurnError } from '../conversation-7VzVDT95.js';
1
+ import { g as ConversationState, j as SendOptions, e as Conversation, L as LainternClient, f as ConversationOptions } from '../citations-BR_LD7jF.js';
2
+ export { a as AnswerSegment, o as Turn, p as TurnAdvisory, q as TurnBlock, r as TurnError, w as isNavigable, x as resolveCitation, y as segmentAnswer } from '../citations-BR_LD7jF.js';
3
3
 
4
4
  interface UseLainternChatResult extends ConversationState {
5
5
  send: (message: string, options?: SendOptions) => Promise<void>;
@@ -1,4 +1,5 @@
1
- import { createConversation } from '../chunk-WMKNNVMS.js';
1
+ import { createConversation } from '../chunk-OSMAH2QV.js';
2
+ export { isNavigable, resolveCitation, segmentAnswer } from '../chunk-OSMAH2QV.js';
2
3
  import { useRef, useMemo, useSyncExternalStore, useCallback } from 'react';
3
4
 
4
5
  function useLainternChat(client, options = {}) {
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/react/index.ts"],"names":[],"mappings":";;;AA8BO,SAAS,eAAA,CACd,MAAA,EACA,OAAA,GAA+B,EAAC,EACT;AACvB,EAAA,MAAM,UAAA,GAAa,OAAO,OAAO,CAAA;AACjC,EAAA,MAAM,YAAA,GAAe,OAAA;AAAA,IACnB,MAAM,kBAAA,CAAmB,MAAA,EAAQ,UAAA,CAAW,OAAO,CAAA;AAAA,IACnD,CAAC,MAAM;AAAA,GACT;AAEA,EAAA,MAAM,KAAA,GAAQ,oBAAA;AAAA,IACZ,WAAA,CAAY,CAAC,QAAA,KAAa,YAAA,CAAa,UAAU,QAAQ,CAAA,EAAG,CAAC,YAAY,CAAC,CAAA;AAAA,IAC1E,YAAY,MAAM,YAAA,CAAa,UAAS,EAAG,CAAC,YAAY,CAAC,CAAA;AAAA,IACzD,YAAY,MAAM,YAAA,CAAa,UAAS,EAAG,CAAC,YAAY,CAAC;AAAA,GAC3D;AAEA,EAAA,MAAM,IAAA,GAAO,WAAA;AAAA,IACX,CAAC,OAAA,EAAiB,WAAA,KAA8B,YAAA,CAAa,IAAA,CAAK,SAAS,WAAW,CAAA;AAAA,IACtF,CAAC,YAAY;AAAA,GACf;AACA,EAAA,MAAM,KAAA,GAAQ,YAAY,MAAM,YAAA,CAAa,OAAM,EAAG,CAAC,YAAY,CAAC,CAAA;AACpE,EAAA,MAAM,KAAA,GAAQ,YAAY,MAAM,YAAA,CAAa,OAAM,EAAG,CAAC,YAAY,CAAC,CAAA;AACpE,EAAA,MAAM,WAAA,GAAc,WAAA;AAAA,IAClB,CAAC,MAAA,EAAgB,QAAA,KACf,YAAA,CAAa,WAAA,CAAY,QAAQ,QAAQ,CAAA;AAAA,IAC3C,CAAC,YAAY;AAAA,GACf;AAEA,EAAA,OAAO,EAAE,GAAG,KAAA,EAAO,MAAM,KAAA,EAAO,KAAA,EAAO,aAAa,YAAA,EAAa;AACnE","file":"index.js","sourcesContent":["import { useCallback, useMemo, useRef, useSyncExternalStore } from \"react\"\nimport { createConversation } from \"../conversation.js\"\nimport type {\n Conversation,\n ConversationOptions,\n ConversationState,\n SendOptions,\n} from \"../conversation.js\"\nimport type { LainternClient } from \"../client.js\"\n\nexport interface UseLainternChatResult extends ConversationState {\n send: (message: string, options?: SendOptions) => Promise<void>\n abort: () => void\n reset: () => void\n setFeedback: (turnId: string, feedback: \"positive\" | \"negative\") => Promise<void>\n /** The underlying store, if you need it outside React. */\n conversation: Conversation\n}\n\n/**\n * React binding for {@link createConversation}.\n *\n * The conversation lives outside React and is read through\n * `useSyncExternalStore`, so a streaming answer re-renders only the components\n * that read it — and the transcript survives a parent re-render.\n *\n * The client and options are captured on first render: pass a client created\n * once (module scope, or a `useMemo` in your provider), not a fresh one per\n * render.\n */\nexport function useLainternChat(\n client: LainternClient,\n options: ConversationOptions = {},\n): UseLainternChatResult {\n const optionsRef = useRef(options)\n const conversation = useMemo(\n () => createConversation(client, optionsRef.current),\n [client],\n )\n\n const state = useSyncExternalStore(\n useCallback((listener) => conversation.subscribe(listener), [conversation]),\n useCallback(() => conversation.getState(), [conversation]),\n useCallback(() => conversation.getState(), [conversation]),\n )\n\n const send = useCallback(\n (message: string, sendOptions?: SendOptions) => conversation.send(message, sendOptions),\n [conversation],\n )\n const abort = useCallback(() => conversation.abort(), [conversation])\n const reset = useCallback(() => conversation.reset(), [conversation])\n const setFeedback = useCallback(\n (turnId: string, feedback: \"positive\" | \"negative\") =>\n conversation.setFeedback(turnId, feedback),\n [conversation],\n )\n\n return { ...state, send, abort, reset, setFeedback, conversation }\n}\n\nexport type { Conversation, ConversationOptions, ConversationState, SendOptions } from \"../conversation.js\"\nexport type { Turn, TurnAdvisory, TurnBlock, TurnError } from \"../conversation.js\"\n"]}
1
+ {"version":3,"sources":["../../src/react/index.ts"],"names":[],"mappings":";;;;AA8BO,SAAS,eAAA,CACd,MAAA,EACA,OAAA,GAA+B,EAAC,EACT;AACvB,EAAA,MAAM,UAAA,GAAa,OAAO,OAAO,CAAA;AACjC,EAAA,MAAM,YAAA,GAAe,OAAA;AAAA,IACnB,MAAM,kBAAA,CAAmB,MAAA,EAAQ,UAAA,CAAW,OAAO,CAAA;AAAA,IACnD,CAAC,MAAM;AAAA,GACT;AAEA,EAAA,MAAM,KAAA,GAAQ,oBAAA;AAAA,IACZ,WAAA,CAAY,CAAC,QAAA,KAAa,YAAA,CAAa,UAAU,QAAQ,CAAA,EAAG,CAAC,YAAY,CAAC,CAAA;AAAA,IAC1E,YAAY,MAAM,YAAA,CAAa,UAAS,EAAG,CAAC,YAAY,CAAC,CAAA;AAAA,IACzD,YAAY,MAAM,YAAA,CAAa,UAAS,EAAG,CAAC,YAAY,CAAC;AAAA,GAC3D;AAEA,EAAA,MAAM,IAAA,GAAO,WAAA;AAAA,IACX,CAAC,OAAA,EAAiB,WAAA,KAA8B,YAAA,CAAa,IAAA,CAAK,SAAS,WAAW,CAAA;AAAA,IACtF,CAAC,YAAY;AAAA,GACf;AACA,EAAA,MAAM,KAAA,GAAQ,YAAY,MAAM,YAAA,CAAa,OAAM,EAAG,CAAC,YAAY,CAAC,CAAA;AACpE,EAAA,MAAM,KAAA,GAAQ,YAAY,MAAM,YAAA,CAAa,OAAM,EAAG,CAAC,YAAY,CAAC,CAAA;AACpE,EAAA,MAAM,WAAA,GAAc,WAAA;AAAA,IAClB,CAAC,MAAA,EAAgB,QAAA,KACf,YAAA,CAAa,WAAA,CAAY,QAAQ,QAAQ,CAAA;AAAA,IAC3C,CAAC,YAAY;AAAA,GACf;AAEA,EAAA,OAAO,EAAE,GAAG,KAAA,EAAO,MAAM,KAAA,EAAO,KAAA,EAAO,aAAa,YAAA,EAAa;AACnE","file":"index.js","sourcesContent":["import { useCallback, useMemo, useRef, useSyncExternalStore } from \"react\"\nimport { createConversation } from \"../conversation.js\"\nimport type {\n Conversation,\n ConversationOptions,\n ConversationState,\n SendOptions,\n} from \"../conversation.js\"\nimport type { LainternClient } from \"../client.js\"\n\nexport interface UseLainternChatResult extends ConversationState {\n send: (message: string, options?: SendOptions) => Promise<void>\n abort: () => void\n reset: () => void\n setFeedback: (turnId: string, feedback: \"positive\" | \"negative\") => Promise<void>\n /** The underlying store, if you need it outside React. */\n conversation: Conversation\n}\n\n/**\n * React binding for {@link createConversation}.\n *\n * The conversation lives outside React and is read through\n * `useSyncExternalStore`, so a streaming answer re-renders only the components\n * that read it — and the transcript survives a parent re-render.\n *\n * The client and options are captured on first render: pass a client created\n * once (module scope, or a `useMemo` in your provider), not a fresh one per\n * render.\n */\nexport function useLainternChat(\n client: LainternClient,\n options: ConversationOptions = {},\n): UseLainternChatResult {\n const optionsRef = useRef(options)\n const conversation = useMemo(\n () => createConversation(client, optionsRef.current),\n [client],\n )\n\n const state = useSyncExternalStore(\n useCallback((listener) => conversation.subscribe(listener), [conversation]),\n useCallback(() => conversation.getState(), [conversation]),\n useCallback(() => conversation.getState(), [conversation]),\n )\n\n const send = useCallback(\n (message: string, sendOptions?: SendOptions) => conversation.send(message, sendOptions),\n [conversation],\n )\n const abort = useCallback(() => conversation.abort(), [conversation])\n const reset = useCallback(() => conversation.reset(), [conversation])\n const setFeedback = useCallback(\n (turnId: string, feedback: \"positive\" | \"negative\") =>\n conversation.setFeedback(turnId, feedback),\n [conversation],\n )\n\n return { ...state, send, abort, reset, setFeedback, conversation }\n}\n\nexport type { Conversation, ConversationOptions, ConversationState, SendOptions } from \"../conversation.js\"\nexport type { Turn, TurnAdvisory, TurnBlock, TurnError } from \"../conversation.js\"\nexport { isNavigable, resolveCitation, segmentAnswer } from \"../citations.js\"\nexport type { AnswerSegment } from \"../citations.js\"\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@laintern/chat-sdk",
3
- "version": "0.1.0",
3
+ "version": "0.1.1",
4
4
  "description": "Client SDK for talking to a Laintern chat agent: token lifecycle, streaming, citations and sources.",
5
5
  "license": "UNLICENSED",
6
6
  "type": "module",
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/errors.ts","../src/conversation.ts"],"names":[],"mappings":"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","file":"chunk-WMKNNVMS.js","sourcesContent":["/**\n * Every failure the SDK surfaces, with the API's own error code attached.\n *\n * Callers switch on `code` rather than parsing messages: the server's copy is\n * user-facing prose that may change, while the code is part of the contract.\n */\nexport class LainternError extends Error {\n readonly code: string\n readonly status: number | null\n /** Set on RATE_LIMIT_EXCEEDED. */\n readonly rateLimitScope?: \"user\" | \"agent\"\n readonly rateLimitWindow?: \"minute\" | \"hour\" | \"day\"\n /** Set on MCP_UNAVAILABLE: which dependency failed, and how. */\n readonly reason?: string\n\n constructor(\n code: string,\n message: string,\n options: {\n status?: number | null\n rateLimitScope?: \"user\" | \"agent\"\n rateLimitWindow?: \"minute\" | \"hour\" | \"day\"\n reason?: string\n cause?: unknown\n } = {},\n ) {\n super(message, options.cause !== undefined ? { cause: options.cause } : undefined)\n this.name = \"LainternError\"\n this.code = code\n this.status = options.status ?? null\n if (options.rateLimitScope) this.rateLimitScope = options.rateLimitScope\n if (options.rateLimitWindow) this.rateLimitWindow = options.rateLimitWindow\n if (options.reason) this.reason = options.reason\n }\n\n /**\n * True when retrying later could plausibly succeed: a rate limit, or a\n * dependency the server could not reach. An expired session is NOT retryable\n * — the host has to mint a new token first.\n */\n get isRetryable(): boolean {\n return this.code === \"RATE_LIMIT_EXCEEDED\" || this.reason === \"transport\"\n }\n}\n\nfunction pick<T extends string>(value: unknown, allowed: readonly T[]): T | undefined {\n return typeof value === \"string\" && (allowed as readonly string[]).includes(value)\n ? (value as T)\n : undefined\n}\n\n/** Turn a non-2xx API response into a `LainternError`. Never throws itself. */\nexport async function errorFromResponse(response: Response): Promise<LainternError> {\n let code = `HTTP_${response.status}`\n let message = \"An unexpected error occurred.\"\n let rateLimitScope: \"user\" | \"agent\" | undefined\n let rateLimitWindow: \"minute\" | \"hour\" | \"day\" | undefined\n let reason: string | undefined\n\n try {\n const body = (await response.json()) as {\n error?: {\n code?: string\n message?: string\n scope?: unknown\n window?: unknown\n reason?: unknown\n }\n message?: string\n }\n if (body?.error?.code) {\n code = body.error.code\n message = body.error.message ?? message\n rateLimitScope = pick(body.error.scope, [\"user\", \"agent\"] as const)\n rateLimitWindow = pick(body.error.window, [\"minute\", \"hour\", \"day\"] as const)\n if (typeof body.error.reason === \"string\") reason = body.error.reason\n } else if (typeof body?.message === \"string\") {\n message = body.message\n }\n } catch {\n // Not JSON — keep the HTTP-derived defaults.\n }\n\n return new LainternError(code, message, {\n status: response.status,\n rateLimitScope,\n rateLimitWindow,\n reason,\n })\n}\n","import type {\n CitationSource,\n ExternalSourceType,\n SearchResult,\n StepEvent,\n} from \"@laintern-headless/types\"\nimport type { LainternClient } from \"./client.js\"\nimport { LainternError } from \"./errors.js\"\n\n/** Soft advisory alongside an answer — the agent flagged the topic, but answered. */\nexport interface TurnAdvisory {\n redirect: string | null\n message: string\n}\n\n/** A guardrail refused the turn. The agent produced no answer. */\nexport interface TurnBlock {\n /** Which guardrail fired, when the server names one. */\n slug: string | null\n /** Copy meant for the end user, written by the guardrail's configuration. */\n message: string\n}\n\nexport interface TurnError {\n code: string\n message: string\n rateLimitScope?: \"user\" | \"agent\"\n rateLimitWindow?: \"minute\" | \"hour\" | \"day\"\n}\n\nexport interface Turn {\n id: string\n userMessage: string\n userImages?: string[]\n assistantContent: string\n /** Citation number (the `[1]` in the text) → the source it points at. */\n citations: Map<number, CitationSource>\n /** Everything the agent cited or consulted, in the order the server ranked it. */\n sources: SearchResult[]\n /** Sidebar suggestions per content type — not necessarily cited. */\n externalSources: Partial<Record<ExternalSourceType, SearchResult[]>>\n mode: string | null\n steps: StepEvent[]\n isStreaming: boolean\n error?: TurnError\n blocked?: TurnBlock\n advisory?: TurnAdvisory\n messageId?: string\n feedback?: \"positive\" | \"negative\" | null\n}\n\nexport interface ConversationState {\n turns: Turn[]\n sessionId: string | null\n isStreaming: boolean\n}\n\nexport interface SendOptions {\n images?: string[]\n forceTool?: string\n}\n\nexport interface Conversation {\n getState(): ConversationState\n /** Subscribe to state changes. Returns an unsubscribe function. */\n subscribe(listener: (state: ConversationState) => void): () => void\n /** Send a message and stream the answer into state. Never throws. */\n send(message: string, options?: SendOptions): Promise<void>\n /** Stop the current stream. The partial answer stays in state. */\n abort(): void\n /** Drop all turns and start a new server-side session on the next send. */\n reset(): void\n setFeedback(turnId: string, feedback: \"positive\" | \"negative\"): Promise<void>\n}\n\nexport interface ConversationOptions {\n /** Resume an existing server-side session. */\n sessionId?: string | null\n /** Restore turns from your own storage. */\n initialTurns?: Turn[]\n /** Called whenever the session id changes, so you can persist it. */\n onSessionId?: (sessionId: string) => void\n}\n\nfunction newId(): string {\n const c = globalThis.crypto\n if (c && typeof c.randomUUID === \"function\") return c.randomUUID()\n return `turn-${Date.now()}-${Math.random().toString(36).slice(2, 10)}`\n}\n\nfunction createTurn(message: string, images?: string[]): Turn {\n return {\n id: newId(),\n userMessage: message,\n ...(images?.length ? { userImages: images } : {}),\n assistantContent: \"\",\n citations: new Map(),\n sources: [],\n externalSources: {},\n mode: null,\n steps: [],\n isStreaming: true,\n }\n}\n\n/**\n * Stateful conversation on top of {@link LainternClient}.\n *\n * Holds the turn list, applies each stream event to the turn in flight, and\n * notifies subscribers. Framework-agnostic on purpose: the React binding in\n * `@laintern/chat-sdk/react` is a thin `useSyncExternalStore` wrapper, and any\n * other UI layer can subscribe the same way.\n *\n * `send()` never throws. A failed turn is part of the conversation — it lands\n * on `turn.error` (or `turn.blocked` for a guardrail) so the UI can render it\n * in place, which is what you want in a chat transcript.\n */\nexport function createConversation(\n client: LainternClient,\n options: ConversationOptions = {},\n): Conversation {\n let state: ConversationState = {\n turns: options.initialTurns ?? [],\n sessionId: options.sessionId ?? null,\n isStreaming: false,\n }\n const listeners = new Set<(state: ConversationState) => void>()\n let controller: AbortController | null = null\n\n function emit(next: ConversationState): void {\n state = next\n for (const listener of listeners) listener(state)\n }\n\n /** Apply a change to the turn currently streaming (always the last one). */\n function patchLast(patch: (turn: Turn) => Turn): void {\n const lastIndex = state.turns.length - 1\n if (lastIndex < 0) return\n const turns = [...state.turns]\n turns[lastIndex] = patch(turns[lastIndex])\n emit({ ...state, turns })\n }\n\n return {\n getState: () => state,\n\n subscribe(listener) {\n listeners.add(listener)\n return () => listeners.delete(listener)\n },\n\n abort() {\n controller?.abort()\n },\n\n reset() {\n controller?.abort()\n emit({ turns: [], sessionId: null, isStreaming: false })\n },\n\n async send(message, sendOptions = {}) {\n controller?.abort()\n controller = new AbortController()\n const signal = controller.signal\n\n emit({\n ...state,\n turns: [...state.turns, createTurn(message, sendOptions.images)],\n isStreaming: true,\n })\n\n try {\n const stream = client.sendMessage(message, {\n sessionId: state.sessionId,\n ...(sendOptions.images ? { images: sendOptions.images } : {}),\n ...(sendOptions.forceTool ? { forceTool: sendOptions.forceTool } : {}),\n signal,\n })\n\n for await (const event of stream) {\n if (signal.aborted) break\n\n switch (event.type) {\n case \"session\":\n emit({ ...state, sessionId: event.session_id })\n options.onSessionId?.(event.session_id)\n break\n case \"mode\":\n patchLast((turn) => ({ ...turn, mode: event.mode }))\n break\n case \"step\":\n patchLast((turn) => ({ ...turn, steps: mergeStep(turn.steps, event.step) }))\n break\n case \"delta\":\n patchLast((turn) => ({\n ...turn,\n assistantContent: turn.assistantContent + event.content,\n }))\n break\n case \"citation\":\n patchLast((turn) => {\n const citations = new Map(turn.citations)\n citations.set(event.index, event.source)\n return { ...turn, citations }\n })\n break\n case \"sources\":\n patchLast((turn) => ({ ...turn, sources: event.items }))\n break\n case \"external_sources\":\n patchLast((turn) => ({\n ...turn,\n externalSources: {\n ...turn.externalSources,\n [event.source_type]: event.items,\n },\n }))\n break\n case \"advisory\":\n patchLast((turn) => ({\n ...turn,\n advisory: { redirect: event.redirect ?? null, message: event.message },\n }))\n break\n case \"done\":\n patchLast((turn) => ({\n ...turn,\n isStreaming: false,\n ...(event.message_id ? { messageId: event.message_id } : {}),\n }))\n emit({ ...state, isStreaming: false })\n break\n case \"error\":\n applyFailure(patchLast, {\n code: event.code,\n message: event.message,\n guardrailSlug: event.guardrail_slug ?? null,\n })\n break\n }\n }\n } catch (err) {\n if ((err as Error)?.name === \"AbortError\") {\n patchLast((turn) => ({ ...turn, isStreaming: false }))\n } else if (err instanceof LainternError) {\n applyFailure(patchLast, {\n code: err.code,\n message: err.message,\n guardrailSlug: null,\n ...(err.rateLimitScope ? { rateLimitScope: err.rateLimitScope } : {}),\n ...(err.rateLimitWindow ? { rateLimitWindow: err.rateLimitWindow } : {}),\n })\n } else {\n applyFailure(patchLast, {\n code: \"NETWORK_ERROR\",\n message: err instanceof Error ? err.message : \"An unknown error occurred.\",\n guardrailSlug: null,\n })\n }\n } finally {\n // A turn always stops streaming, however it ended — including an abort\n // that broke the loop without throwing. Leaving the flag set strands a\n // typing indicator on a turn that will never produce another token.\n const last = state.turns.at(-1)\n if (last?.isStreaming) patchLast((turn) => ({ ...turn, isStreaming: false }))\n if (state.isStreaming) emit({ ...state, isStreaming: false })\n controller = null\n }\n },\n\n async setFeedback(turnId, feedback) {\n const turn = state.turns.find((t) => t.id === turnId)\n if (!turn?.messageId) {\n throw new LainternError(\n \"FEEDBACK_UNAVAILABLE\",\n \"This turn has no message id yet — feedback is only possible once a turn completes.\",\n )\n }\n await client.submitFeedback(turn.messageId, feedback)\n emit({\n ...state,\n turns: state.turns.map((t) => (t.id === turnId ? { ...t, feedback } : t)),\n })\n },\n }\n}\n\n/**\n * A completed step replaces its own \"started\" entry rather than appending, so\n * the UI shows one row per tool call that fills in, not two.\n */\nfunction mergeStep(steps: StepEvent[], step: StepEvent): StepEvent[] {\n if (step.status !== \"completed\") return [...steps, step]\n const startedIndex = steps.findIndex((s) => s.tool === step.tool && s.status === \"started\")\n if (startedIndex === -1) return [...steps, step]\n const next = [...steps]\n next[startedIndex] = step\n return next\n}\n\n/**\n * A failed or blocked turn.\n *\n * A guardrail block discards whatever streamed before it fired: the partial\n * answer is exactly the content the guardrail decided the user should not see,\n * and a hard block always wins over a soft advisory that arrived a moment\n * earlier.\n */\nfunction applyFailure(\n patchLast: (patch: (turn: Turn) => Turn) => void,\n failure: {\n code: string\n message: string\n guardrailSlug: string | null\n rateLimitScope?: \"user\" | \"agent\"\n rateLimitWindow?: \"minute\" | \"hour\" | \"day\"\n },\n): void {\n if (failure.code === \"GUARDRAIL_TRIGGERED\") {\n patchLast((turn) => ({\n ...turn,\n isStreaming: false,\n assistantContent: \"\",\n citations: new Map(),\n sources: [],\n externalSources: {},\n steps: [],\n mode: null,\n advisory: undefined,\n blocked: { slug: failure.guardrailSlug, message: failure.message },\n }))\n return\n }\n\n patchLast((turn) => ({\n ...turn,\n isStreaming: false,\n error: {\n code: failure.code,\n message: failure.message,\n ...(failure.rateLimitScope ? { rateLimitScope: failure.rateLimitScope } : {}),\n ...(failure.rateLimitWindow ? { rateLimitWindow: failure.rateLimitWindow } : {}),\n },\n }))\n}\n"]}