@anuma/sdk 10.9.0 → 10.10.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -20,7 +20,7 @@ import {
20
20
  taskTypeHeader,
21
21
  validateEndpointOverride,
22
22
  withInternalFlowMarker
23
- } from "./chunk-4SOUKJD5.mjs";
23
+ } from "./chunk-VNTEHEGI.mjs";
24
24
  import {
25
25
  BASE_URL,
26
26
  MCP_R2_DOMAIN,
@@ -54,8 +54,8 @@ function generateCodeVerifier() {
54
54
  return base64UrlEncode(array);
55
55
  }
56
56
  async function generateCodeChallenge(verifier) {
57
- const encoder = new TextEncoder();
58
- const data = encoder.encode(verifier);
57
+ const encoder2 = new TextEncoder();
58
+ const data = encoder2.encode(verifier);
59
59
  const hash = await crypto.subtle.digest("SHA-256", data);
60
60
  return base64UrlEncode(new Uint8Array(hash));
61
61
  }
@@ -3031,7 +3031,7 @@ async function updateMessageErrorOp(ctx, uniqueId, error) {
3031
3031
  }
3032
3032
  await ctx.database.write(async () => {
3033
3033
  await message.update((msg) => {
3034
- msg._setRaw("error", error);
3034
+ msg._setRaw("error", error ?? "");
3035
3035
  });
3036
3036
  });
3037
3037
  return messageToStored(message, ctx.walletAddress, ctx.signMessage, ctx.embeddedWalletSigner);
@@ -5088,382 +5088,1255 @@ function createCryptoPricePreProcessor(options = {}) {
5088
5088
  };
5089
5089
  }
5090
5090
 
5091
- // src/lib/chat/reservationReceipts.ts
5092
- var ACTIONS = [
5093
- {
5094
- kind: "booking",
5095
- tool: "anuma_book_restaurant",
5096
- alreadyDone: /^Tool "[^"]*" failed: ALREADY_BOOKED:/
5097
- },
5098
- {
5099
- kind: "cancel",
5100
- tool: "anuma_cancel_reservation",
5101
- alreadyDone: /^Tool "[^"]*" failed: ALREADY_CANCELLED:/
5102
- }
5091
+ // src/lib/db/entities/types.ts
5092
+ var ENTITY_KINDS = [
5093
+ "person",
5094
+ "organization",
5095
+ "place",
5096
+ "event",
5097
+ "product",
5098
+ "thing",
5099
+ "concept",
5100
+ "other"
5103
5101
  ];
5104
- var NOT_AN_ATTEMPT = /^Tool "[^"]*" (has already been called|was already called)/;
5105
- var BOOKING_OUTCOME_UNKNOWN = /^Tool "[^"]*" failed: BOOKING_OUTCOME_UNKNOWN:/;
5106
- var CANCEL_OUTCOME_UNKNOWN = "CANCEL_OUTCOME_UNKNOWN";
5107
- function extractReservationReceipts(toolCallEvents) {
5108
- const latest = /* @__PURE__ */ new Map();
5109
- for (const event of toolCallEvents ?? []) {
5110
- const action = ACTIONS.find((a) => event.name?.endsWith(a.tool));
5111
- const output = event.output ?? "";
5112
- if (!action || !output || hasEncryptedToolPayload(event) || NOT_AN_ATTEMPT.test(output))
5113
- continue;
5114
- const kept = latest.get(action.kind)?.status;
5115
- if (kept === "made") continue;
5116
- const receipt = toReceipt(action, event.arguments ?? "", output);
5117
- if (kept === "unknown" && receipt.status !== "made") continue;
5118
- latest.set(action.kind, receipt);
5119
- }
5120
- return [...latest.values()];
5121
- }
5122
- function toReceipt(action, argumentsJson, output) {
5123
- const args = parseObject(argumentsJson);
5124
- const result = parseObject(output);
5125
- return {
5126
- kind: action.kind,
5127
- venueName: text2(args.venue_name) ?? "",
5128
- day: text2(args.day) ?? "",
5129
- time: text2(args.time) ?? "",
5130
- partySize: count(args.party_size),
5131
- reservationId: text2(result.reservation_id) ?? text2(args.reservation_id),
5132
- resyUrl: text2(result.reservations_url),
5133
- feeSummary: text2(result.fee_summary),
5134
- ...outcome(action, output, result)
5135
- };
5102
+ function normalizeEntityName(name) {
5103
+ return name.trim().toLowerCase();
5136
5104
  }
5137
- function outcome(action, output, result) {
5138
- if (action.alreadyDone.test(output)) return { status: "already_done" };
5139
- const reason = text2(result.error);
5140
- if (action.kind === "booking") {
5141
- if (BOOKING_OUTCOME_UNKNOWN.test(output)) return { status: "unknown" };
5142
- const held = result.success === true || text2(result.reservation_id) !== void 0;
5143
- return held ? { status: "made" } : { status: "not_made", reason };
5144
- }
5145
- if (result.error_code === CANCEL_OUTCOME_UNKNOWN) return { status: "unknown", reason };
5146
- const cancelled = result.success === true && !result.error && !result.error_code;
5147
- return cancelled ? { status: "made" } : { status: "not_made", reason };
5105
+ var TOPIC_SALIENCE_WEIGHTS = { primary: 1, secondary: 0.6, mention: 0.3 };
5106
+ function topicWeight(topic2) {
5107
+ return topic2.source === "user" ? 1 : topic2.weight ?? 1;
5148
5108
  }
5149
- function parseObject(json5) {
5150
- try {
5151
- const parsed = JSON.parse(json5);
5152
- return parsed && typeof parsed === "object" ? parsed : {};
5153
- } catch {
5154
- return {};
5109
+ function primaryTopic(topics) {
5110
+ let best = null;
5111
+ for (const topic2 of topics ?? []) {
5112
+ if (best === null || topicWeight(topic2) > topicWeight(best)) best = topic2;
5155
5113
  }
5114
+ return best;
5156
5115
  }
5157
- function text2(value) {
5158
- if (typeof value === "number") return String(value);
5159
- return typeof value === "string" && value ? value : void 0;
5160
- }
5161
- function count(value) {
5162
- const n = typeof value === "string" && value.trim() ? Number(value) : value;
5163
- return typeof n === "number" && Number.isFinite(n) ? n : void 0;
5164
- }
5165
-
5166
- // src/tools/confirmConstants.ts
5167
- var CONFIRM_TOOL_NAME = "prompt_user_confirm";
5168
- var BOOK_RESTAURANT = {
5169
- tool: "AnumaPaymentsMCP-anuma_book_restaurant",
5170
- status: "confirmed_not_booked",
5171
- fields: ["venue_id", "config_id", "party_size", "day", "venue_name", "time"]
5172
- };
5173
- var CANCEL_RESERVATION = {
5174
- tool: "AnumaPaymentsMCP-anuma_cancel_reservation",
5175
- status: "confirmed_not_cancelled",
5176
- fields: [
5177
- "reservation_id",
5178
- "venue_name",
5179
- "day",
5180
- "time",
5181
- "party_size",
5182
- "fee_applies",
5183
- "fee_amount"
5184
- ]
5185
- };
5186
- var CONFIRM_REQUIRED_FIELDS = /* @__PURE__ */ new Map([
5187
- ["book_restaurant", BOOK_RESTAURANT],
5188
- ["anuma_book_restaurant", BOOK_RESTAURANT],
5189
- ["anumapaymentsmcp-anuma_book_restaurant", BOOK_RESTAURANT],
5190
- ["cancel_reservation", CANCEL_RESERVATION],
5191
- ["anuma_cancel_reservation", CANCEL_RESERVATION],
5192
- ["anumapaymentsmcp-anuma_cancel_reservation", CANCEL_RESERVATION]
5193
- ]);
5194
- function confirmRequirement(action) {
5195
- if (typeof action !== "string") return void 0;
5196
- return CONFIRM_REQUIRED_FIELDS.get(action.trim().toLowerCase());
5116
+ function parseTopicWeight(value) {
5117
+ return typeof value === "number" && value > 0 && value <= 1 ? value : void 0;
5197
5118
  }
5198
- function missingConfirmFields(action, parameters) {
5199
- const requirement = confirmRequirement(action);
5200
- if (!requirement) return [];
5201
- const shown = /* @__PURE__ */ new Set();
5202
- for (const parameter of Array.isArray(parameters) ? parameters : []) {
5203
- const { name, value } = parameter ?? {};
5204
- if (typeof name === "string" && typeof value === "string" && value.trim()) shown.add(name);
5205
- }
5206
- return requirement.fields.filter((field10) => !shown.has(field10));
5119
+ function serializeTopics(topics) {
5120
+ return JSON.stringify(topics);
5207
5121
  }
5208
-
5209
- // src/tools/walletPaymentConstants.ts
5210
- var SELECT_PAYMENT_WALLET_TOOL_NAME = "select_payment_wallet";
5211
- var WALLET_PAYMENT_TOOL_NAME = "prompt_wallet_payment";
5212
- var FLIGHT_BOOK_TOOL_SET = "flight-book";
5213
- var FLIGHT_SERVER_TOOL_NAMES = [
5214
- "AnumaPaymentsMCP-anuma_search_flights",
5215
- "AnumaPaymentsMCP-anuma_flight_offer_details",
5216
- "AnumaPaymentsMCP-anuma_create_flight_intent",
5217
- "AnumaPaymentsMCP-anuma_get_flight_booking"
5218
- ];
5219
-
5220
- // src/lib/storage/mcpImages.ts
5221
- var IMAGE_TOOL_NAMES = /* @__PURE__ */ new Set([
5222
- "AnumaMediaMCP-anuma_create_image",
5223
- "anuma_create_image",
5224
- "AnumaImageMCP-generate_cloud_image",
5225
- "AnumaImageMCP-edit_cloud_image",
5226
- "generate_cloud_image",
5227
- "edit_cloud_image"
5228
- ]);
5229
- function toolOutputForModel(name, output) {
5230
- if (!name || !IMAGE_TOOL_NAMES.has(name)) return output;
5122
+ function parseTopics(value) {
5123
+ if (typeof value !== "string" || value.length === 0) return null;
5124
+ let parsed;
5231
5125
  try {
5232
- const {
5233
- imageUrl: _imageUrl,
5234
- url: _url,
5235
- output_images: _outputImages,
5236
- ...rest
5237
- } = JSON.parse(output);
5238
- return JSON.stringify(rest);
5126
+ parsed = JSON.parse(value);
5239
5127
  } catch {
5240
- return output;
5128
+ return null;
5129
+ }
5130
+ if (!Array.isArray(parsed)) return null;
5131
+ const out = [];
5132
+ for (const raw of parsed) {
5133
+ if (typeof raw !== "object" || raw === null) continue;
5134
+ const { name, kind, source, weight } = raw;
5135
+ if (typeof name !== "string" || name.trim().length === 0) continue;
5136
+ const parsedWeight = parseTopicWeight(weight);
5137
+ out.push({
5138
+ name,
5139
+ ...typeof kind === "string" && kind.length > 0 ? { kind } : {},
5140
+ source: source === "user" ? "user" : "auto",
5141
+ ...parsedWeight !== void 0 ? { weight: parsedWeight } : {}
5142
+ });
5241
5143
  }
5144
+ return out;
5242
5145
  }
5243
- var VIDEO_EXTENSIONS = ["mp4", "webm", "mov"];
5244
- var VIDEO_EXTENSION_RE = new RegExp(`\\.(${VIDEO_EXTENSIONS.join("|")})(?:[?#]|$)`, "i");
5245
-
5246
- // src/tools/appBuilderPrompt.ts
5247
- var APP_BUILDER_PROMPT = `App Builder tools (create_file, patch_file, and the rest described below) are available this turn, but they are OPTIONAL \u2014 having them does not mean you should build anything.
5248
-
5249
- Build ONLY when the user gives an explicit instruction to build, create, generate, or change an app, tool, or UI \u2014 e.g. "build a todo app", "make a snake game", "add a footer to the app", "change the background to blue". A bare app name or feature reference is NOT a build instruction: if the user just says "todo app", "a snake game", or "qr code", or asks a question about an app, do NOT generate anything \u2014 reply normally, or ask what they'd like you to build. The same holds for greetings, questions, stories, explanations, advice, or code that isn't a runnable app: respond normally and do not create files. When it's unclear whether the user actually wants an app built, ask instead of building.
5250
-
5251
- Once the user HAS given an explicit build instruction, you are in App Builder mode: you produce polished, production-quality React apps that feel designed \u2014 not generic.
5252
-
5253
- DESIGN: on the initial build, before writing code, briefly state (2-3 sentences) what you want this app to feel like \u2014 name the specific fonts, the palette in hex, and one signature detail. Be concrete: "Fraunces display + Inter body + JetBrains Mono metadata, terracotta #b75432 accent on bone #f3efe6, paper-grain texture via inline SVG noise" is the kind of specificity that anchors the build. Default to nothing: don't pick "safe modern productivity" unless the brief literally asks for utilitarian. On later turns, state a design brief only when the change is visual (a restyle, new theme, or new UI); for a text, logic, data, or behavior-only change, skip the brief and just make the change.
5254
-
5255
- EDIT SCOPE: Change only the files and lines required by the current user request. A title rename is a text edit, not a design change. Preserve styles, dependencies, and unrelated logic on text edits. Design feedback does not authorize unrelated edits.
5256
-
5257
- WORKFLOW:
5258
- 1. NEVER output code as text or markdown. ALWAYS use tools.
5259
- 2. Initial build: state your design brief, then create_file with a "files" array writing ALL files in one call. The preview renders automatically.
5260
- 3. patch_file requires read_file for that path earlier in the conversation (or create_file in the same conversation, which counts). read_file output is numbered "42: <text>" \u2014 the prefix is display-only; never copy it into "find". Each "find" must match exactly one location \u2014 include 2-3 lines of surrounding context. Pass multiple patches per call to change distinct locations.
5261
- 4. Prefer patch_file for changes to existing files \u2014 surgical, reviewable, preserves surrounding code. Reserve create_file overwrites for substantial restructuring. Inserts: find the line before, replace with that line + the new line. Deletes: empty replace.
5262
- 5. On the initial build, when adding new UI, or after user-requested design changes, call critique_design first and then audit_design. Critique checks taste, hierarchy, and distinctiveness. Audit checks tokens, focus states, aria-labels, alt text, and heading order. Patch only issues within the current user request. For text, logic, or data edits, skip both design tools.
5263
- 6. Before declaring done, call verify_app. The host's preview runtime reports whether your app actually mounted and surfaces any errors it captured \u2014 hallucinated imports, syntax the bundler rejected, undefined components, runtime crashes. If \`rendered\` is false or \`errors\` is non-empty, fix every error before the next message. If the response includes a \`note\` that verify_app isn't wired in this host, skip runtime verification. An unavailable runtime verifier does not authorize design changes. Use audit_design and critique_design only for the initial build, adding new UI, or user-requested design changes.
5264
- 7. Keep responses to a sentence or two. Exception: when responding to critique_design, write a substantive paragraph per rubric question.
5265
-
5266
- STRUCTURE:
5267
- - App.js: default-export React component. For non-trivial apps, define small co-located helpers (Header, CardList, Card, EditDialog, \u2026) above the default export rather than one giant App.
5268
- - App.css: all styles, imported in App.js. No inline style objects.
5269
- - package.json: list ALL imported packages including react. No CDN script tags. Versions are auto-pinned.
5270
- - Do NOT create index.js or index.html (auto-generated).
5271
-
5272
- STATE & PERSISTENCE:
5273
- - The code runs in the user's browser \u2014 standard browser APIs available (localStorage, URL hash, Date, fetch).
5274
- - Persist user-created data (todos, cards, notes, settings) with localStorage so it survives refresh. Use a versioned key ("kanban.v1") and JSON.parse inside try/catch on read.
5275
- - Treat persisted data as untrusted and possibly stale: a saved record may predate a field you just added (a timestamp, id, or setting), so default-fill or skip missing fields on read \u2014 never assume a loaded record has every field the current code expects. Bump the key version when the shape changes.
5276
- - Lift state to App when multiple children need it; keep it local otherwise. Extract non-trivial logic into custom hooks (useTodos, useTimer).
5277
- - useReducer for state shapes with several interrelated fields (cards + columns + filters + drag state).
5278
-
5279
- FILE & IMAGE UPLOAD:
5280
- - Picker: \`<input type="file" accept="image/*" multiple>\` (vary accept by use case: \`.csv,text/csv\`, \`.txt,.md,text/plain\`, etc.).
5281
- - Drag-and-drop: call \`e.preventDefault()\` in onDragOver (otherwise drop never fires); read files from \`event.dataTransfer.files\`. Drop and picker should coexist.
5282
- - Previews: \`<img src={URL.createObjectURL(file)} />\`. Call \`URL.revokeObjectURL\` on unmount or before replacing \u2014 otherwise the browser leaks memory.
5283
- - localStorage cannot hold large binary. Persist only metadata for big files; re-upload on next visit (or use IndexedDB if you really need durable binary storage).
5284
- - Validate size and type before processing; reject files larger than the app can hold in memory.
5285
-
5286
- AI CAPABILITIES \u2014 \`window.app.complete(prompt: string): Promise<string>\` is available in this environment. Build for the real call.
5287
- - When the app's core purpose is conversation, generation, grading, recommendation, or any natural-language task, this IS the implementation \u2014 call it. NEVER fake it: no hardcoded or canned replies, no random or \`setTimeout\` "thinking", no demo-mode stub. A chat app, tutor, or assistant that doesn't call \`window.app.complete\` is wrong, not a placeholder.
5288
- - Don't force it where it isn't needed \u2014 a calculator, todo list, or timer has no reason to call it. Match the implementation to what the app actually does.
5289
- - Async: wrap in try/catch, show a loading state, and disable dependent inputs while waiting. Surface failures gracefully ("Couldn't reach the AI \u2014 try again").
5290
- - Structure prompts with clear sections (Role / Context / Task / Format) and pass the relevant state (e.g. the running conversation) as context. Render the response as untrusted text: \`white-space: pre-wrap\`, never dangerouslySetInnerHTML.
5291
- - Skip the call only when the answer is genuinely static \u2014 no language or reasoning needed. Cache repeated prompts in component state when sensible.
5292
-
5293
- CODE QUALITY \u2014 write as a senior engineer would:
5294
- - Handle edge cases (empty inputs, division by zero, invalid data). Show helpful error states, not crashes.
5295
- - Never let formatting throw during render: guard \`new Date(x)\` / \`Intl\` / \`toLocaleString\` / \`.toFixed\` against missing or invalid values (e.g. \`Number.isNaN(d.getTime())\`) and return a fallback \u2014 one bad value from storage or state must not blank the whole app.
5296
- - Semantic HTML: real headings, labels, buttons (not divs with onClick), form elements.
5297
- - Accessible: aria-labels on icon buttons, focus-visible styles, keyboard navigable.
5298
- - Extract into custom hooks or helpers when a component grows past ~80 lines.
5299
-
5300
- STYLING \u2014 Tailwind is the CHASSIS, custom CSS carries the IDENTITY:
5301
- - Tailwind (via Play CDN) for layout, spacing, state (hover/focus), responsive prefixes \u2014 uniform structure.
5302
- - App.css carries the identity: @import the Google Fonts, declare CSS custom properties (--bg, --panel, --card, --ink, --muted, --accent, --hairline, --radius, --radius-sm, --shadow-card, --shadow-card-hi) in :root, gradients/textures/signature animations Tailwind can't express. Reference tokens from Tailwind via arbitrary values (\`className="bg-[var(--bg)]"\`) when needed. A well-designed app has 60-200 lines of App.css \u2014 not zero.
5303
- - Modern CSS is how apps stop reading as generic-AI-output: \`color-mix(in oklab, var(--accent) 14%, transparent)\` for tinting, \`oklch()\` / \`oklab()\` for perceptually-uniform color, \`:has()\`, \`conic-gradient\` / \`radial-gradient\`, \`backdrop-filter\`, \`@container\` queries, inline SVG \`feTurbulence\` for texture.
5304
- - Don't pull additional CSS frameworks. Tailwind + custom CSS is enough.
5305
-
5306
- VISUAL DESIGN \u2014 every app should look like a real product, not a wireframe:
5307
- - Let the chosen aesthetic drive every decision. "Editorial" wants italic display type and restrained color; "retro arcade" wants chunky shadows and saturated gradients. No safe-modern defaults.
5308
- - Atmospheric depth: subtle radial wash, paper grain (inline SVG noise), warm/cool tint \u2014 every canvas should feel like a designed space, not a #fff rectangle.
5309
- - Three shadow tiers (rest / hover / active). Pair drop shadows with inset highlights (\`box-shadow: 0 1px 0 rgba(255,255,255,.9) inset, 0 6px 14px -10px ...\`) for tactile feel.
5310
- - Typography hierarchy: display font for headlines (italic accents on key words via \`<em>\`), body font for content, mono for metadata/labels \u2014 different families telegraph different information types.
5311
- - Interactive polish: hover/active/focus on every clickable element, 200-300ms cubic-bezier transitions, keyboard shortcuts where natural (Esc to cancel, Cmd/Ctrl+Enter to commit), focus-visible rings keyed to the accent.
5312
- - Light/dark mode if reasonable: a single CSS variable inversion under \`[data-theme="dark"]\`.
5313
- - Customization affordance for multi-session apps: small "Tweaks" panel with theme / density / accent. Skip for throwaway demos.
5314
- - Responsive: Tailwind responsive prefixes (\`sm:\`, \`md:\`, \`lg:\`) and \`@media\` in App.css. Mobile-first.`;
5315
-
5316
- // src/tools/document/documentBuilderPrompt.ts
5317
- var DOCUMENT_BUILDER_PROMPT = `Document tools (create_document, read_document, patch_document) are available this turn, but they are OPTIONAL \u2014 having them does not mean you should produce a document.
5318
-
5319
- Use them ONLY when the user explicitly asks you to draft, write, compose, or generate a document \u2014 a contract, agreement, NDA, letter, cover letter, memo, essay, report, brief, proposal, or resume \u2014 e.g. "draft me a service agreement", "write a cover letter for this job", "make a one-page memo about X". A bare topic, a question, a request for an explanation, or a request for a slide deck or an interactive app is NOT a document instruction: respond normally and do not call these tools. When it's unclear whether the user wants a finished document, ask instead.
5320
-
5321
- When you DO author a document, the document body goes entirely into the tool \u2014 NEVER write the document text as chat prose. The tool renders your source to a real, paginated PDF and attaches it to your reply automatically. AFTER the tool returns, briefly confirm in ONE short sentence what you made (e.g. "Drafted a cover letter for the product manager role \u2014 see the attached PDF."). Do not restate the document's contents.
5322
-
5323
- AUTHORING \u2014 write the "source" as a single @react-pdf/renderer JSX expression (this is react-pdf, NOT HTML and NOT a slide deck):
5324
- - Root is exactly one <Document> with at least one <Page>. Its only children are <Page> elements. Add more <Page> elements for longer documents \u2014 react-pdf also paginates overflow automatically.
5325
- - Inside a <Page>: use <View> as flex containers and <Text> for ALL text. Raw text is only allowed inside <Text> (and <Link>); a <View> or <Page> may not contain bare text.
5326
- - <Text> may nest <Text>, <Link>, or <Note> for inline styling, hyperlinks, and (rarely) a PDF comment annotation. A <Link> src must be an ordinary web link \u2014 http(s), mailto, tel, or a relative/anchor link; javascript:/data:/file: links are rejected. <Image> is self-closing and its src MUST be an inline "data:" URI \u2014 remote image URLs are rejected for privacy. Vector primitives (<Rect>/<Circle>/<Path>/etc.) must live inside an <Svg>.
5327
- - Styling is react-pdf style objects only: style={{ marginTop: 12, fontSize: 11, fontFamily: "Times-Roman", flexDirection: "column" }}. Flexbox only \u2014 there is NO CSS grid, float, or box-shadow. Use camelCase react-pdf keys (margin/padding, flex*, width/height, color/backgroundColor, border*, text*, etc.).
5328
- - fontFamily must be a built-in PDF font: Helvetica, Times-Roman, or Courier (with -Bold / -Oblique / -Italic / -BoldOblique variants), or Symbol / ZapfDingbats. Custom fonts are not supported.
5329
- - LITERAL VALUES ONLY: no variables, no JS expressions, no .map(), no event handlers (onClick, \u2026), no spreads. Every attribute is a literal string, number, boolean, or a style object of literals.
5330
-
5331
- A minimal example:
5332
-
5333
- <Document>
5334
- <Page size="A4" style={{ paddingVertical: 56, paddingHorizontal: 64, fontFamily: "Times-Roman", fontSize: 11, lineHeight: 1.5 }}>
5335
- <Text style={{ fontSize: 18, fontFamily: "Times-Bold", marginBottom: 16, textAlign: "center" }}>MUTUAL NON-DISCLOSURE AGREEMENT</Text>
5336
- <Text style={{ marginBottom: 10 }}>This Mutual Non-Disclosure Agreement (the "Agreement") is entered into as of the date of last signature below.</Text>
5337
- <View style={{ marginTop: 14 }}>
5338
- <Text style={{ fontFamily: "Times-Bold", marginBottom: 4 }}>1. Confidential Information</Text>
5339
- <Text>Each party may disclose certain confidential and proprietary information to the other party.</Text>
5340
- </View>
5341
- </Page>
5342
- </Document>
5343
-
5344
- WORKFLOW:
5345
- 1. To create or fully restructure a document, call create_document with the complete source.
5346
- 2. To revise an existing document, call read_document first (its output is line-numbered "42: <text>" \u2014 the prefix is display-only, never copy it into a "find"), then patch_document with small find/replace edits. Each "find" must match exactly one location; add 2-3 lines of surrounding context if needed. Prefer patching over rewriting.
5347
- 3. To attach several separate documents in one reply, call create_document multiple times with distinct documentId slugs (e.g. "nda", "cover-letter"). Each renders to its own PDF.
5348
- 4. If a tool reports a DSL error (with line:col) or a render failure, fix the source and retry \u2014 do not give up or fall back to writing the document as chat text.`;
5349
5146
 
5350
- // src/lib/tools/serverTools.ts
5351
- var DEFAULT_CACHE_EXPIRATION_MS = 24 * 60 * 60 * 1e3;
5352
- var SERVER_TOOLS_CACHE_KEY = "sdk_server_tools_cache";
5353
- var CACHE_VERSION = "1.3";
5354
- var MIN_CONTENT_LENGTH_FOR_TOOLS = 5;
5355
- var MAX_CLIENT_TOOLS_AFTER_FILTER = 10;
5356
- var CLIENT_TOOLS_MIN_SIMILARITY = 0.53;
5357
- var CLIENT_TOOLS_RELEVANCE_RATIO = 0.75;
5358
- function isNewToolFormat(tool) {
5359
- return "schema" in tool && tool.schema !== void 0;
5360
- }
5361
- function isNewResponseFormat(response) {
5362
- return "checksum" in response && "tools" in response && typeof response.checksum === "string";
5363
- }
5364
- function convertServerToolsResponse(response) {
5365
- let toolsMap;
5366
- let checksum;
5367
- if (isNewResponseFormat(response)) {
5368
- toolsMap = response.tools;
5369
- checksum = response.checksum;
5370
- } else {
5371
- toolsMap = response;
5372
- }
5373
- const tools = Object.values(toolsMap).map((tool) => {
5374
- if (isNewToolFormat(tool)) {
5375
- return {
5376
- type: "function",
5377
- name: tool.schema.name,
5378
- description: tool.schema.description,
5379
- parameters: tool.schema.parameters,
5380
- ...tool.embedding && { embedding: tool.embedding }
5381
- };
5382
- }
5383
- return {
5384
- type: "function",
5385
- name: tool.name,
5386
- description: tool.description,
5387
- parameters: tool.parameters
5388
- };
5389
- });
5390
- return { tools, checksum };
5147
+ // src/lib/memory/store/remoteTopics.ts
5148
+ function topic(value) {
5149
+ const t = value;
5150
+ if (!t || typeof t.name !== "string" || !t.name || !Number.isSafeInteger(t.version) || t.version < 1 || typeof t.is_deleted !== "boolean" || typeof t.server_updated_at !== "string" || t.kind !== void 0 && typeof t.kind !== "string")
5151
+ throw new Error("Invalid nearby topic");
5152
+ return t;
5391
5153
  }
5392
- function toCompletionsFormat(tool) {
5154
+ function createRemoteTopicPersistence(request) {
5393
5155
  return {
5394
- type: "function",
5395
- function: {
5396
- name: tool.name,
5397
- description: tool.description,
5398
- parameters: tool.parameters
5156
+ topics: async () => {
5157
+ const out = [];
5158
+ let cursor;
5159
+ do {
5160
+ const page = await request(
5161
+ "/topics?limit=200" + (cursor ? `&cursor=${encodeURIComponent(cursor)}` : ""),
5162
+ {}
5163
+ );
5164
+ if (!Array.isArray(page.items) || page.next_cursor !== void 0 && typeof page.next_cursor !== "string")
5165
+ throw new Error("Invalid nearby topic page");
5166
+ out.push(...page.items.map(topic));
5167
+ cursor = page.next_cursor;
5168
+ } while (cursor);
5169
+ return out;
5170
+ },
5171
+ putTopic: async (value, expectedVersion) => {
5172
+ if (!Number.isSafeInteger(expectedVersion) || expectedVersion < 0)
5173
+ throw new Error("Expected topic version must be a nonnegative integer");
5174
+ return topic(
5175
+ await request("/topics", {
5176
+ method: "PUT",
5177
+ body: JSON.stringify({ topic: value, expected_version: expectedVersion })
5178
+ })
5179
+ );
5180
+ },
5181
+ importTopics: async (topics, signal) => {
5182
+ for (let i = 0; i < topics.length; i += 50) {
5183
+ signal?.throwIfAborted();
5184
+ await request(
5185
+ "/topics/import",
5186
+ {
5187
+ method: "POST",
5188
+ body: JSON.stringify({ topics: topics.slice(i, i + 50) })
5189
+ },
5190
+ signal
5191
+ );
5192
+ }
5193
+ signal?.throwIfAborted();
5399
5194
  }
5400
5195
  };
5401
5196
  }
5402
- function toResponsesFormat(tool) {
5403
- return {
5404
- type: "function",
5405
- name: tool.name,
5406
- description: tool.description,
5407
- parameters: tool.parameters
5408
- };
5409
- }
5410
- function getCachedServerTools() {
5411
- if (typeof localStorage === "undefined") return null;
5197
+ async function createRemoteTopic(persistence, name) {
5198
+ const canonical = normalizeEntityName(name);
5199
+ if (!canonical) return null;
5200
+ const existing = (await persistence.topics()).find((t) => t.name === canonical);
5201
+ if (existing && !existing.is_deleted) return canonical;
5412
5202
  try {
5413
- const cached = localStorage.getItem(SERVER_TOOLS_CACHE_KEY);
5414
- if (!cached) return null;
5415
- const parsed = JSON.parse(cached);
5416
- if (parsed.version !== CACHE_VERSION) {
5417
- removeLocalStorageCache();
5418
- return null;
5419
- }
5420
- return parsed;
5421
- } catch {
5422
- return null;
5203
+ await persistence.putTopic(
5204
+ { name: canonical, is_deleted: false, ...existing?.kind && { kind: existing.kind } },
5205
+ existing?.version ?? 0
5206
+ );
5207
+ } catch (error) {
5208
+ if (!(error instanceof RemoteMemoryError) || error.code !== "version_conflict") throw error;
5209
+ const current = (await persistence.topics()).find((t) => t.name === canonical);
5210
+ if (!current || current.is_deleted) throw error;
5423
5211
  }
5212
+ return canonical;
5424
5213
  }
5425
- function isCacheExpired(cache, expirationMs = DEFAULT_CACHE_EXPIRATION_MS) {
5426
- if (!cache) return true;
5427
- return Date.now() - cache.timestamp > expirationMs;
5428
- }
5429
- function buildCacheEntry(tools, checksum, etag) {
5214
+
5215
+ // src/lib/memory/store/remoteWork.ts
5216
+ function createRemoteWorkPersistence(request) {
5430
5217
  return {
5431
- tools,
5432
- timestamp: Date.now(),
5433
- version: CACHE_VERSION,
5434
- ...checksum && { checksum },
5435
- ...etag && { etag }
5218
+ claimWork: async (kind, processorVersion, token) => {
5219
+ const result = await request("/work/claim", {
5220
+ method: "POST",
5221
+ body: JSON.stringify({
5222
+ kind,
5223
+ processor_version: processorVersion,
5224
+ ...token && { token }
5225
+ })
5226
+ });
5227
+ if (!result || typeof result !== "object") throw new Error("Invalid nearby work lease");
5228
+ const lease = result.lease;
5229
+ if (lease === void 0) return null;
5230
+ if (!lease || lease.kind !== kind || typeof lease.token !== "string" || !lease.token || lease.processor_version !== processorVersion || typeof lease.expires_at !== "string" || !Number.isFinite(Date.parse(lease.expires_at)))
5231
+ throw new Error("Invalid nearby work lease");
5232
+ return lease;
5233
+ },
5234
+ releaseWork: async (kind, token) => {
5235
+ await request("/work/release", { method: "POST", body: JSON.stringify({ kind, token }) });
5236
+ }
5436
5237
  };
5437
5238
  }
5438
- function writeLocalStorageCache(entry) {
5439
- if (typeof localStorage === "undefined") return;
5239
+ async function withRemoteWorkLease(persistence, kind, processorVersion, task) {
5240
+ const lease = await persistence.claimWork(kind, processorVersion);
5241
+ if (!lease) return null;
5242
+ const controller = new AbortController();
5243
+ const leaseLostError = new RemoteMemoryError("Maintenance lease lost", 409, "work_lease_expired");
5244
+ const cancelled = new Promise((_resolve, reject) => {
5245
+ controller.signal.addEventListener("abort", () => reject(leaseLostError), {
5246
+ once: true
5247
+ });
5248
+ });
5249
+ let renewing;
5250
+ const timer = setInterval(() => {
5251
+ if (renewing || controller.signal.aborted) return;
5252
+ renewing = persistence.claimWork(kind, processorVersion, lease.token).then((renewed) => {
5253
+ if (!renewed) controller.abort(leaseLostError);
5254
+ }).catch((error) => getLogger().warn("[memory/work] Lease renewal failed:", error)).finally(() => {
5255
+ renewing = void 0;
5256
+ });
5257
+ }, 3e4);
5440
5258
  try {
5441
- localStorage.setItem(SERVER_TOOLS_CACHE_KEY, JSON.stringify(entry));
5259
+ const result = await Promise.race([task(lease.token, controller.signal), cancelled]);
5260
+ await renewing;
5261
+ return controller.signal.aborted ? null : result;
5442
5262
  } catch (error) {
5443
- getLogger().warn("[serverTools] Failed to cache tools:", error);
5263
+ if (controller.signal.aborted || isRemoteWorkLeaseExpired(error)) return null;
5264
+ throw error;
5265
+ } finally {
5266
+ clearInterval(timer);
5267
+ await renewing;
5268
+ await persistence.releaseWork(kind, lease.token).catch((error) => getLogger().warn("[memory/work] Lease release failed:", error));
5444
5269
  }
5445
5270
  }
5446
- function removeLocalStorageCache() {
5447
- if (typeof localStorage === "undefined") return;
5448
- localStorage.removeItem(SERVER_TOOLS_CACHE_KEY);
5449
- }
5450
- var localStorageToolsCache = {
5451
- get: getCachedServerTools,
5452
- set: writeLocalStorageCache,
5453
- clear: removeLocalStorageCache
5454
- };
5455
- function clearServerToolsCache(cache = localStorageToolsCache) {
5456
- return cache.clear?.();
5457
- }
5458
- function getToolsChecksum2(cache = localStorageToolsCache) {
5459
- const cached = cache.get();
5460
- return cached instanceof Promise ? cached.then((c) => c?.checksum) : cached?.checksum;
5271
+ function isRemoteWorkLeaseExpired(error) {
5272
+ return error instanceof RemoteMemoryError && error.code === "work_lease_expired";
5461
5273
  }
5462
- function shouldRefreshTools(responseChecksum, cache = localStorageToolsCache) {
5463
- if (!responseChecksum) {
5464
- return false;
5274
+
5275
+ // src/lib/memory/store/remotePersistence.ts
5276
+ var RemoteMemoryError = class extends Error {
5277
+ constructor(message, status, code, retryAfterMs, rateLimitBucket) {
5278
+ super(message);
5279
+ this.status = status;
5280
+ this.code = code;
5281
+ this.retryAfterMs = retryAfterMs;
5282
+ this.rateLimitBucket = rateLimitBucket;
5283
+ this.name = "RemoteMemoryError";
5465
5284
  }
5466
- const decide = (cachedChecksum2) => !cachedChecksum2 || cachedChecksum2 !== responseChecksum;
5285
+ };
5286
+ var ciphertextPattern = /^enc:v\d+:[0-9a-f]+$/i;
5287
+ var memoryFields = {
5288
+ memory_id: true,
5289
+ content: true,
5290
+ scope: true,
5291
+ created_at: true,
5292
+ updated_at: true,
5293
+ is_deleted: true,
5294
+ embedding: true,
5295
+ embedding_model: true,
5296
+ kind: true,
5297
+ kind_value: true,
5298
+ level: true,
5299
+ folder_id: true,
5300
+ source_chunk_ids: true,
5301
+ proof_count: true,
5302
+ source: true,
5303
+ event_time_start: true,
5304
+ event_time_end: true,
5305
+ event_time_kind: true,
5306
+ topics_user_managed: true,
5307
+ topics: true,
5308
+ topics_updated_at: true,
5309
+ media: true,
5310
+ topics_extracted_at: true,
5311
+ topics_extracted_version: true,
5312
+ superseded_by: true,
5313
+ superseded_at: true,
5314
+ last_observed_at: true,
5315
+ fact_type: true,
5316
+ archived_at: true,
5317
+ trust_tier: true,
5318
+ visibility: true,
5319
+ twin_opt_in: true,
5320
+ published_at: true,
5321
+ geohash: true
5322
+ };
5323
+ var candidateListLimits = [
5324
+ ["scopes", 20],
5325
+ ["fact_types", 20],
5326
+ ["memory_ids", 1e3],
5327
+ ["force_ids", 100]
5328
+ ];
5329
+ var queryListLimits = [
5330
+ ["scopes", 20],
5331
+ ["fact_types", 20],
5332
+ ["visibility", 20],
5333
+ ["memory_ids", 1e3],
5334
+ ["topics", 100]
5335
+ ];
5336
+ var queryFields = [
5337
+ "include_deleted",
5338
+ "include_archived",
5339
+ "include_quarantined",
5340
+ "include_superseded",
5341
+ "archived_only",
5342
+ "scopes",
5343
+ "fact_types",
5344
+ "visibility",
5345
+ "memory_ids",
5346
+ "topics",
5347
+ "updated_after",
5348
+ "topics_due",
5349
+ "topics_extracted",
5350
+ "event_time_overlaps",
5351
+ "order",
5352
+ "cursor",
5353
+ "limit",
5354
+ "include_embeddings"
5355
+ ];
5356
+ function record(value) {
5357
+ return typeof value === "object" && value !== null && !Array.isArray(value);
5358
+ }
5359
+ function memoryRecord(value) {
5360
+ if (!record(value) || !record(value.memory) || typeof value.memory.memory_id !== "string" || typeof value.memory.content !== "string" || typeof value.memory.scope !== "string" || typeof value.memory.created_at !== "number" || typeof value.memory.updated_at !== "number" || typeof value.memory.is_deleted !== "boolean" || !Number.isSafeInteger(value.version) || value.version < 1 || typeof value.server_updated_at !== "string" || value.score !== void 0 && (typeof value.score !== "number" || !Number.isFinite(value.score))) {
5361
+ throw new Error("Invalid nearby private-memory response");
5362
+ }
5363
+ return value;
5364
+ }
5365
+ function createNearbyRequest(options, apiPath = "/api/private-memories") {
5366
+ const base = new URL(options.baseUrl);
5367
+ if (!/^https?:$/.test(base.protocol) || base.username || base.password || base.search || base.hash) {
5368
+ throw new Error(
5369
+ "baseUrl must be an HTTP(S) origin or base path without credentials, query or fragment"
5370
+ );
5371
+ }
5372
+ const timeoutMs = options.timeoutMs ?? 3e4;
5373
+ if (!Number.isFinite(timeoutMs) || timeoutMs <= 0)
5374
+ throw new Error("timeoutMs must be a positive number");
5375
+ const root = base.href.replace(/\/$/, "") + apiPath;
5376
+ const fetcher = options.fetch ?? globalThis.fetch;
5377
+ const cooldowns = /* @__PURE__ */ new Map();
5378
+ const memoryBudget = (path, method) => {
5379
+ if (method === "PUT")
5380
+ return path === "/topics" ? "private-memory-import" : "private-memory-write";
5381
+ if (method === "POST" && ["/import", "/memories/batch", "/topics/import"].includes(path))
5382
+ return "private-memory-import";
5383
+ return "private-memory-read";
5384
+ };
5385
+ const request = async (path, init = {}, signal) => {
5386
+ const controller = new AbortController();
5387
+ let timedOut = false;
5388
+ const timer = setTimeout(() => {
5389
+ timedOut = true;
5390
+ controller.abort();
5391
+ }, timeoutMs);
5392
+ const forwardAbort = () => controller.abort(signal?.reason);
5393
+ if (signal?.aborted) forwardAbort();
5394
+ else signal?.addEventListener("abort", forwardAbort, { once: true });
5395
+ let rejectAborted;
5396
+ const aborted2 = new Promise((_, reject) => {
5397
+ rejectAborted = () => reject(
5398
+ controller.signal.reason instanceof Error ? controller.signal.reason : new Error("Nearby request aborted")
5399
+ );
5400
+ if (controller.signal.aborted) rejectAborted();
5401
+ else controller.signal.addEventListener("abort", rejectAborted, { once: true });
5402
+ });
5403
+ aborted2.catch(() => void 0);
5404
+ try {
5405
+ const token = await Promise.race([options.getToken(), aborted2]);
5406
+ if (controller.signal.aborted) await aborted2;
5407
+ if (!token) throw new RemoteMemoryError("Authentication required", 401);
5408
+ const pathname = path.split("?")[0];
5409
+ const route = `${init.method ?? "GET"} ${pathname}`;
5410
+ const budgets = ["*", route];
5411
+ if (apiPath === "/api/private-memories")
5412
+ budgets.push("private-memory-ip", memoryBudget(pathname, init.method ?? "GET"));
5413
+ for (const bucket of budgets) {
5414
+ const cooldown = cooldowns.get(bucket);
5415
+ if (!cooldown) continue;
5416
+ const remaining = cooldown.until - Date.now();
5417
+ if (remaining > 0)
5418
+ throw new RemoteMemoryError(
5419
+ cooldown.error.message,
5420
+ 429,
5421
+ cooldown.error.code,
5422
+ remaining,
5423
+ cooldown.error.rateLimitBucket
5424
+ );
5425
+ cooldowns.delete(bucket);
5426
+ }
5427
+ const response = await fetcher(root + path, {
5428
+ ...init,
5429
+ headers: {
5430
+ Authorization: `Bearer ${token}`,
5431
+ ...init.body ? { "Content-Type": "application/json" } : {}
5432
+ },
5433
+ redirect: "error",
5434
+ signal: controller.signal
5435
+ });
5436
+ if (!response.ok) {
5437
+ const body = await response.json().catch(() => null);
5438
+ const retryAfter = parseRetryAfterMs(response.headers.get("Retry-After"));
5439
+ const error = new RemoteMemoryError(
5440
+ record(body) && typeof body.detail === "string" ? body.detail : `Nearby request failed (${response.status})`,
5441
+ response.status,
5442
+ record(body) && typeof body.code === "string" ? body.code : void 0,
5443
+ retryAfter ?? void 0,
5444
+ record(body) && typeof body.rate_limit_bucket === "string" ? body.rate_limit_bucket : void 0
5445
+ );
5446
+ if (response.status === 429 && retryAfter !== null && retryAfter > 0 && (!error.code || error.code === "rate_limited")) {
5447
+ const bucket = error.rateLimitBucket && budgets.includes(error.rateLimitBucket) ? error.rateLimitBucket : record(body) && (body.limit_scope === "account" || body.limit_scope === "ip") && !error.rateLimitBucket ? "*" : route;
5448
+ cooldowns.set(bucket, { until: Date.now() + retryAfter, error });
5449
+ }
5450
+ throw error;
5451
+ }
5452
+ if (response.status === 204) return null;
5453
+ return await response.json();
5454
+ } catch (error) {
5455
+ if (timedOut) throw new RemoteMemoryError("Nearby request timed out", 408, "timeout");
5456
+ throw error;
5457
+ } finally {
5458
+ clearTimeout(timer);
5459
+ signal?.removeEventListener("abort", forwardAbort);
5460
+ if (rejectAborted) controller.signal.removeEventListener("abort", rejectAborted);
5461
+ }
5462
+ };
5463
+ return request;
5464
+ }
5465
+ async function createRemoteMemoryPersistence(options) {
5466
+ if (!options.keyId || options.keyId.length > 256)
5467
+ throw new Error("A canonical keyId is required");
5468
+ const request = createNearbyRequest(options);
5469
+ const account = await request("/account", {}, options.signal);
5470
+ if (!record(account) || account.state !== "active") {
5471
+ throw new RemoteMemoryError(
5472
+ "Complete and verify migration before using remote memories",
5473
+ 409,
5474
+ "memory_not_active"
5475
+ );
5476
+ }
5477
+ if (account.key_id !== options.keyId) {
5478
+ throw new RemoteMemoryError(
5479
+ "The canonical memory key does not match this account",
5480
+ 409,
5481
+ "key_mismatch"
5482
+ );
5483
+ }
5484
+ const snapshots = /* @__PURE__ */ new WeakMap();
5485
+ const decode = async (value) => {
5486
+ const item = memoryRecord(value);
5487
+ const decrypt = async (ciphertext) => {
5488
+ if (!ciphertextPattern.test(ciphertext))
5489
+ throw new Error("Nearby returned unencrypted memory content");
5490
+ const plaintext = await options.decrypt(ciphertext);
5491
+ if (plaintext === ciphertext) throw new Error("Memory decryption failed");
5492
+ return plaintext;
5493
+ };
5494
+ const decoded = {
5495
+ ...item,
5496
+ memory: {
5497
+ ...item.memory,
5498
+ content: item.memory.is_deleted && item.memory.content === "" ? "" : await decrypt(item.memory.content),
5499
+ ...item.memory.kind_value !== void 0 && {
5500
+ kind_value: await decrypt(item.memory.kind_value)
5501
+ }
5502
+ }
5503
+ };
5504
+ return remember(decoded, item);
5505
+ };
5506
+ const remember = (decoded, item) => {
5507
+ snapshots.set(decoded, {
5508
+ id: item.memory.memory_id,
5509
+ version: item.version,
5510
+ content: decoded.memory.content,
5511
+ kindValue: decoded.memory.kind_value,
5512
+ encryptedContent: item.memory.content,
5513
+ encryptedKindValue: item.memory.kind_value
5514
+ });
5515
+ return decoded;
5516
+ };
5517
+ const decodeAll = async (values) => {
5518
+ const records = values.map(memoryRecord);
5519
+ const settled = await Promise.allSettled(records.map(decode));
5520
+ const items = [];
5521
+ const failed = [];
5522
+ settled.forEach((result, i) => {
5523
+ if (result.status === "fulfilled") items.push(result.value);
5524
+ else
5525
+ failed.push({
5526
+ memory_id: records[i].memory.memory_id,
5527
+ version: records[i].version,
5528
+ error: result.reason instanceof Error ? result.reason : new Error(String(result.reason))
5529
+ });
5530
+ });
5531
+ return { items, failed };
5532
+ };
5533
+ const decodeCommitted = async (value, write) => {
5534
+ const item = memoryRecord(value);
5535
+ if (item.memory.memory_id !== write.body.memory.memory_id || item.memory.content !== write.body.memory.content || item.memory.kind_value !== write.body.memory.kind_value)
5536
+ return decode(item);
5537
+ return remember(
5538
+ {
5539
+ ...item,
5540
+ memory: {
5541
+ ...item.memory,
5542
+ content: write.content,
5543
+ ...write.kindValue !== void 0 && { kind_value: write.kindValue }
5544
+ }
5545
+ },
5546
+ item
5547
+ );
5548
+ };
5549
+ const encryptField2 = async (plaintext) => {
5550
+ const ciphertext = await options.encrypt(plaintext);
5551
+ if (ciphertext === plaintext || !ciphertextPattern.test(ciphertext))
5552
+ throw new Error("Field encryption must return SDK ciphertext; refusing to upload plaintext");
5553
+ return ciphertext;
5554
+ };
5555
+ const memoryPath = (id2) => {
5556
+ if (!id2 || id2.length > 128) throw new Error("memory_id must be 1\u2013128 characters");
5557
+ return "/memories/" + encodeURIComponent(id2);
5558
+ };
5559
+ const prepareWrite = async (memory, expectedVersion, signal) => {
5560
+ const unknown = Object.keys(memory).filter(
5561
+ (key3) => !Object.prototype.hasOwnProperty.call(memoryFields, key3)
5562
+ );
5563
+ if (unknown.length) {
5564
+ throw new RemoteMemoryError(
5565
+ `Unknown private-memory fields (${unknown.join(", ")}); refusing to forward them`,
5566
+ 400,
5567
+ "unknown_field"
5568
+ );
5569
+ }
5570
+ memory = { ...memory, ...memory.embedding && { embedding: [...memory.embedding] } };
5571
+ const path = memoryPath(memory.memory_id);
5572
+ const snapshot = typeof expectedVersion === "number" ? void 0 : snapshots.get(expectedVersion);
5573
+ if (typeof expectedVersion !== "number" && (!snapshot || snapshot.id !== memory.memory_id || snapshot.version !== expectedVersion.version))
5574
+ throw new Error(
5575
+ "Expected snapshot must be an unmodified version from this persistence instance"
5576
+ );
5577
+ expectedVersion = typeof expectedVersion === "number" ? expectedVersion : snapshot.version;
5578
+ if (!Number.isSafeInteger(expectedVersion) || expectedVersion < 0)
5579
+ throw new Error("expectedVersion must be a nonnegative integer");
5580
+ const conflict2 = () => new RemoteMemoryError("The memory changed since it was read", 409, "version_conflict");
5581
+ let existing;
5582
+ if (expectedVersion !== 0 && !snapshot) {
5583
+ try {
5584
+ existing = memoryRecord(await request(path, {}, signal));
5585
+ } catch (error) {
5586
+ if (error instanceof RemoteMemoryError && error.status === 404) throw conflict2();
5587
+ throw error;
5588
+ }
5589
+ }
5590
+ if (existing && existing.version !== expectedVersion) throw conflict2();
5591
+ const decoded = existing ? await decode(existing).catch(() => void 0) : void 0;
5592
+ const reusableContent = snapshot?.encryptedContent ?? existing?.memory.content;
5593
+ const content = reusableContent && (snapshot?.content ?? decoded?.memory.content) === memory.content ? reusableContent : await encryptField2(memory.content);
5594
+ const kindValue = memory.kind_value === void 0 ? void 0 : (snapshot?.kindValue ?? decoded?.memory.kind_value) === memory.kind_value ? snapshot?.encryptedKindValue ?? existing.memory.kind_value : await encryptField2(memory.kind_value);
5595
+ return {
5596
+ body: {
5597
+ expected_version: expectedVersion,
5598
+ memory: { ...memory, content, ...kindValue !== void 0 && { kind_value: kindValue } }
5599
+ },
5600
+ content: memory.content,
5601
+ kindValue: memory.kind_value
5602
+ };
5603
+ };
5604
+ const fetchCandidates = async (embedding2, candidateOptions, withCounts) => {
5605
+ const limit = candidateOptions.limit ?? 100;
5606
+ if (!Number.isInteger(limit) || limit < 1 || limit > 100)
5607
+ throw new Error("Candidate limit must be 1\u2013100");
5608
+ for (const [key3, max] of candidateListLimits) {
5609
+ const values = candidateOptions[key3];
5610
+ if (values && values.length > max)
5611
+ throw new Error(`Candidate ${key3} accepts at most ${max} entries (got ${values.length})`);
5612
+ }
5613
+ const result = await request(
5614
+ "/candidates",
5615
+ {
5616
+ method: "POST",
5617
+ body: JSON.stringify({
5618
+ embedding: embedding2,
5619
+ limit,
5620
+ force_ids: candidateOptions.force_ids ?? [],
5621
+ ...withCounts && { with_counts: true },
5622
+ ...Object.fromEntries(
5623
+ [
5624
+ "scopes",
5625
+ "memory_ids",
5626
+ "embedding_model",
5627
+ "strict_model",
5628
+ "include_deleted",
5629
+ "deleted_only"
5630
+ ].filter(
5631
+ (key3) => candidateOptions[key3] !== void 0
5632
+ ).map((key3) => [key3, candidateOptions[key3]])
5633
+ ),
5634
+ ...candidateOptions.fact_types !== void 0 && {
5635
+ fact_types: candidateOptions.fact_types
5636
+ },
5637
+ ...candidateOptions.include_archived !== void 0 && {
5638
+ include_archived: candidateOptions.include_archived
5639
+ },
5640
+ ...candidateOptions.include_quarantined !== void 0 && {
5641
+ include_quarantined: candidateOptions.include_quarantined
5642
+ },
5643
+ ...candidateOptions.include_superseded !== void 0 && {
5644
+ include_superseded: candidateOptions.include_superseded
5645
+ }
5646
+ })
5647
+ },
5648
+ candidateOptions.signal
5649
+ );
5650
+ if (!record(result) || !Array.isArray(result.items))
5651
+ throw new Error("Invalid nearby candidate response");
5652
+ return result;
5653
+ };
5654
+ const fetchQueryPage = async (queryOptions) => {
5655
+ const { signal, ...query } = queryOptions;
5656
+ if (query.limit !== void 0 && (!Number.isInteger(query.limit) || query.limit < 1 || query.limit > 200))
5657
+ throw new Error("Page limit must be 1\u2013200");
5658
+ for (const [key3, max] of queryListLimits) {
5659
+ const values = query[key3];
5660
+ if (values && values.length > max)
5661
+ throw new Error(`Query ${key3} accepts at most ${max} entries (got ${values.length})`);
5662
+ }
5663
+ const page = await request(
5664
+ "/memories/query",
5665
+ {
5666
+ method: "POST",
5667
+ body: JSON.stringify(
5668
+ Object.fromEntries(
5669
+ queryFields.filter((key3) => query[key3] !== void 0).map((key3) => [key3, query[key3]])
5670
+ )
5671
+ )
5672
+ },
5673
+ signal
5674
+ );
5675
+ if (!record(page) || !Array.isArray(page.items) || page.next_cursor !== void 0 && typeof page.next_cursor !== "string" || page.changes_cursor !== void 0 && typeof page.changes_cursor !== "string")
5676
+ throw new Error("Invalid nearby private-memory page");
5677
+ return page;
5678
+ };
5679
+ const candidateSet = async (embedding2, candidateOptions = {}) => {
5680
+ const result = await fetchCandidates(embedding2, candidateOptions, true);
5681
+ if (!Number.isSafeInteger(result.total_count) || result.total_count < 0 || !Number.isSafeInteger(result.unavailable_count) || result.unavailable_count < 0)
5682
+ throw new Error("Invalid nearby candidate response");
5683
+ return {
5684
+ ...await decodeAll(result.items),
5685
+ total_count: result.total_count,
5686
+ unavailable_count: result.unavailable_count
5687
+ };
5688
+ };
5689
+ return {
5690
+ ...createRemoteTopicPersistence(request),
5691
+ ...createRemoteWorkPersistence(request),
5692
+ supportsRealtimeChanges: account.realtime_changes === true,
5693
+ counts: async () => {
5694
+ const result = await request("/counts", {});
5695
+ if (!record(result) || ["live", "archived", "quarantined"].some(
5696
+ (key3) => !Number.isSafeInteger(result[key3]) || result[key3] < 0
5697
+ ))
5698
+ throw new Error("Invalid nearby memory counts");
5699
+ return {
5700
+ live: result.live,
5701
+ archived: result.archived,
5702
+ quarantined: result.quarantined
5703
+ };
5704
+ },
5705
+ temporal: async (window2, limit = 100) => {
5706
+ if (!Number.isFinite(window2.start) || !Number.isFinite(window2.end) || window2.end <= window2.start || !Number.isInteger(limit) || limit < 1 || limit > 100)
5707
+ throw new Error("Temporal window must have end > start and limit must be 1\u2013100");
5708
+ const result = await request("/temporal", {
5709
+ method: "POST",
5710
+ body: JSON.stringify({ ...window2, limit })
5711
+ });
5712
+ if (!record(result) || !Array.isArray(result.items) || result.items.some(
5713
+ (item) => !record(item) || typeof item.memory_id !== "string" || !item.memory_id || typeof item.score !== "number" || !Number.isFinite(item.score) || item.score <= 0 || item.score > 1
5714
+ ))
5715
+ throw new Error("Invalid nearby temporal candidates");
5716
+ return result.items.map((item) => item.memory_id);
5717
+ },
5718
+ get: async (id2, signal) => {
5719
+ try {
5720
+ return await decode(await request(memoryPath(id2), {}, signal));
5721
+ } catch (error) {
5722
+ if (error instanceof RemoteMemoryError && error.status === 404) return null;
5723
+ throw error;
5724
+ }
5725
+ },
5726
+ list: async (listOptions = {}) => {
5727
+ const { signal, ...query } = listOptions;
5728
+ if (query.limit !== void 0 && (!Number.isInteger(query.limit) || query.limit < 1 || query.limit > 200)) {
5729
+ throw new Error("Page limit must be 1\u2013200");
5730
+ }
5731
+ const params = new URLSearchParams();
5732
+ for (const key3 of [
5733
+ "cursor",
5734
+ "limit",
5735
+ "include_deleted",
5736
+ "include_archived",
5737
+ "include_quarantined",
5738
+ "include_superseded",
5739
+ "include_embeddings"
5740
+ ]) {
5741
+ if (query[key3] !== void 0) params.set(key3, String(query[key3]));
5742
+ }
5743
+ const page = await request("/memories?" + params.toString(), {}, signal);
5744
+ if (!record(page) || !Array.isArray(page.items) || page.next_cursor !== void 0 && typeof page.next_cursor !== "string") {
5745
+ throw new Error("Invalid nearby private-memory page");
5746
+ }
5747
+ return {
5748
+ ...await decodeAll(page.items),
5749
+ ...page.next_cursor !== void 0 && { next_cursor: page.next_cursor }
5750
+ };
5751
+ },
5752
+ put: async (memory, expectedVersion, signal) => {
5753
+ const write = await prepareWrite(memory, expectedVersion, signal);
5754
+ return decodeCommitted(
5755
+ await request(
5756
+ memoryPath(write.body.memory.memory_id),
5757
+ {
5758
+ method: "PUT",
5759
+ body: JSON.stringify({ key_id: options.keyId, ...write.body })
5760
+ },
5761
+ signal
5762
+ ),
5763
+ write
5764
+ );
5765
+ },
5766
+ putMany: async (writes, signal, leaseToken) => {
5767
+ if (writes.length < 1 || writes.length > 50 || new Set(writes.map((w) => w.memory.memory_id)).size !== writes.length)
5768
+ throw new Error("Batch must contain 1\u201350 distinct memory ids");
5769
+ const prepared = await Promise.all(
5770
+ writes.map((w) => prepareWrite(w.memory, w.expectedVersion, signal))
5771
+ );
5772
+ const result = await request(
5773
+ "/memories/batch",
5774
+ {
5775
+ method: "POST",
5776
+ body: JSON.stringify({
5777
+ key_id: options.keyId,
5778
+ writes: prepared.map((w) => w.body),
5779
+ ...leaseToken && { lease_token: leaseToken }
5780
+ })
5781
+ },
5782
+ signal
5783
+ );
5784
+ if (!record(result) || !Array.isArray(result.items) || result.items.length !== prepared.length)
5785
+ throw new Error("Invalid nearby batch response");
5786
+ return Promise.all(result.items.map((item, i) => decodeCommitted(item, prepared[i])));
5787
+ },
5788
+ query: async (queryOptions = {}) => {
5789
+ const page = await fetchQueryPage(queryOptions);
5790
+ return {
5791
+ ...await decodeAll(page.items),
5792
+ ...page.next_cursor !== void 0 && { next_cursor: page.next_cursor },
5793
+ ...page.changes_cursor !== void 0 && { changes_cursor: page.changes_cursor }
5794
+ };
5795
+ },
5796
+ queryMetadata: async (queryOptions = {}) => {
5797
+ const page = await fetchQueryPage(queryOptions);
5798
+ return {
5799
+ items: page.items.map((item) => {
5800
+ const {
5801
+ memory: { content: _content, kind_value: _kindValue, ...memory },
5802
+ version,
5803
+ server_updated_at
5804
+ } = memoryRecord(item);
5805
+ return { memory, version, server_updated_at };
5806
+ }),
5807
+ ...page.next_cursor !== void 0 && { next_cursor: page.next_cursor }
5808
+ };
5809
+ },
5810
+ candidateSet,
5811
+ candidates: async (embedding2, candidateOptions = {}) => decodeAll((await fetchCandidates(embedding2, candidateOptions, false)).items)
5812
+ };
5813
+ }
5814
+
5815
+ // src/lib/chat/inbox.ts
5816
+ var ENVELOPE_ALG = "ECDH-ES+HKDF-SHA256+A256GCM";
5817
+ var HKDF_INFO_PREFIX = "anuma-inbox-v1:";
5818
+ var encoder = new TextEncoder();
5819
+ function toBase64Url(bytes4) {
5820
+ let binary = "";
5821
+ for (const byte of bytes4) binary += String.fromCharCode(byte);
5822
+ return btoa(binary).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
5823
+ }
5824
+ function fromBase64Url(value) {
5825
+ if (typeof value !== "string" || !/^[A-Za-z0-9_-]*$/.test(value)) {
5826
+ throw new Error("Inbox envelope field is not base64url");
5827
+ }
5828
+ const padded = value.replace(/-/g, "+").replace(/_/g, "/");
5829
+ const binary = atob(padded + "=".repeat((4 - padded.length % 4) % 4));
5830
+ return Uint8Array.from(binary, (c) => c.charCodeAt(0));
5831
+ }
5832
+ function isInboxItem(value) {
5833
+ if (!value || typeof value !== "object") return false;
5834
+ const v = value;
5835
+ return typeof v.id === "string" && typeof v.sealed === "string";
5836
+ }
5837
+ function createVaultInbox(options) {
5838
+ const request = createNearbyRequest(options, "/api/private-chats");
5839
+ return {
5840
+ putPublicKey: async (publicKey) => {
5841
+ await request("/public-key", {
5842
+ method: "PUT",
5843
+ body: JSON.stringify({ public_key: publicKey })
5844
+ });
5845
+ },
5846
+ list: async () => {
5847
+ const body = await request("/inbox");
5848
+ return Array.isArray(body?.items) ? body.items.filter(isInboxItem) : [];
5849
+ },
5850
+ remove: async (id2) => {
5851
+ await request(`/inbox/${encodeURIComponent(id2)}`, { method: "DELETE" });
5852
+ }
5853
+ };
5854
+ }
5855
+ async function exportInboxPublicKey(publicKey) {
5856
+ return toBase64Url(new Uint8Array(await crypto.subtle.exportKey("raw", publicKey)));
5857
+ }
5858
+ async function openInboxItem(item, keyPair) {
5859
+ const envelope = JSON.parse(item.sealed);
5860
+ if (envelope.v !== 1 || envelope.alg !== ENVELOPE_ALG) {
5861
+ throw new Error("Unsupported inbox envelope");
5862
+ }
5863
+ const epk = fromBase64Url(envelope.epk);
5864
+ const iv = fromBase64Url(envelope.iv);
5865
+ const ciphertext = fromBase64Url(envelope.ct);
5866
+ if (epk.length !== 65 || iv.length !== 12) throw new Error("Malformed inbox envelope");
5867
+ const ephemeral = await crypto.subtle.importKey(
5868
+ "raw",
5869
+ epk,
5870
+ { name: "ECDH", namedCurve: "P-256" },
5871
+ false,
5872
+ []
5873
+ );
5874
+ const shared = await crypto.subtle.deriveBits(
5875
+ { name: "ECDH", public: ephemeral },
5876
+ keyPair.privateKey,
5877
+ 256
5878
+ );
5879
+ const recipient = new Uint8Array(await crypto.subtle.exportKey("raw", keyPair.publicKey));
5880
+ const salt = new Uint8Array(epk.length + recipient.length);
5881
+ salt.set(epk);
5882
+ salt.set(recipient, epk.length);
5883
+ const ikm = await crypto.subtle.importKey("raw", shared, "HKDF", false, ["deriveKey"]);
5884
+ const key3 = await crypto.subtle.deriveKey(
5885
+ { name: "HKDF", hash: "SHA-256", salt, info: encoder.encode(HKDF_INFO_PREFIX + item.id) },
5886
+ ikm,
5887
+ { name: "AES-GCM", length: 256 },
5888
+ false,
5889
+ ["decrypt"]
5890
+ );
5891
+ const plaintext = await crypto.subtle.decrypt(
5892
+ { name: "AES-GCM", iv, additionalData: encoder.encode(item.id) },
5893
+ key3,
5894
+ ciphertext
5895
+ );
5896
+ const opened = JSON.parse(new TextDecoder().decode(plaintext));
5897
+ if (opened.v !== 1 || opened.inference_id !== item.id || !opened.response) {
5898
+ throw new Error("Inbox item does not match its id");
5899
+ }
5900
+ return opened;
5901
+ }
5902
+ function inboxReplaces(row, opened) {
5903
+ if (row.error) return true;
5904
+ return row.wasStopped === true && opened.status === "completed";
5905
+ }
5906
+ async function clearTurnErrors(backend, answer) {
5907
+ const parentId = answer.parentMessageId;
5908
+ if (parentId) {
5909
+ const [parent] = await backend.getMessagesByIds([parentId]);
5910
+ if (parent?.error) await backend.updateMessageError(parentId, null);
5911
+ }
5912
+ if (!answer.error) return answer;
5913
+ return await backend.updateMessageError(answer.uniqueId, null) ?? answer;
5914
+ }
5915
+ async function deliverInboxAnswers(options) {
5916
+ const { inbox, keyPair, backend, store } = options;
5917
+ const results = [];
5918
+ for (const item of await inbox.list()) {
5919
+ let opened;
5920
+ try {
5921
+ opened = await openInboxItem(item, keyPair);
5922
+ } catch (err) {
5923
+ results.push({
5924
+ id: item.id,
5925
+ conversationId: null,
5926
+ status: "unreadable",
5927
+ assistantMessage: null,
5928
+ error: err instanceof Error ? err.message : String(err)
5929
+ });
5930
+ continue;
5931
+ }
5932
+ const base = { id: item.id, conversationId: opened.conversation_id };
5933
+ try {
5934
+ const messageId = opened.assistant_message_id ?? `msg_bg_${opened.inference_id}`;
5935
+ const [found] = await backend.getMessagesByIds([messageId]);
5936
+ const existing = found && inboxReplaces(found, opened) ? void 0 : found;
5937
+ if (!found) {
5938
+ const parent = opened.parent_message_id;
5939
+ const ownsTurn = parent ? (await backend.getMessagesByIds([parent])).length > 0 : await backend.getConversation(opened.conversation_id) !== null;
5940
+ if (!ownsTurn) {
5941
+ results.push({ ...base, status: "elsewhere", assistantMessage: null });
5942
+ continue;
5943
+ }
5944
+ }
5945
+ const stored2 = existing ?? await store(opened, messageId);
5946
+ await inbox.remove(item.id);
5947
+ results.push({
5948
+ ...base,
5949
+ status: existing ? "delivered" : "stored",
5950
+ assistantMessage: stored2
5951
+ });
5952
+ } catch (err) {
5953
+ results.push({
5954
+ ...base,
5955
+ status: "retry",
5956
+ assistantMessage: null,
5957
+ error: err instanceof Error ? err.message : String(err)
5958
+ });
5959
+ }
5960
+ }
5961
+ return results;
5962
+ }
5963
+
5964
+ // src/lib/chat/reservationReceipts.ts
5965
+ var ACTIONS = [
5966
+ {
5967
+ kind: "booking",
5968
+ tool: "anuma_book_restaurant",
5969
+ alreadyDone: /^Tool "[^"]*" failed: ALREADY_BOOKED:/
5970
+ },
5971
+ {
5972
+ kind: "cancel",
5973
+ tool: "anuma_cancel_reservation",
5974
+ alreadyDone: /^Tool "[^"]*" failed: ALREADY_CANCELLED:/
5975
+ }
5976
+ ];
5977
+ var NOT_AN_ATTEMPT = /^Tool "[^"]*" (has already been called|was already called)/;
5978
+ var BOOKING_OUTCOME_UNKNOWN = /^Tool "[^"]*" failed: BOOKING_OUTCOME_UNKNOWN:/;
5979
+ var CANCEL_OUTCOME_UNKNOWN = "CANCEL_OUTCOME_UNKNOWN";
5980
+ function extractReservationReceipts(toolCallEvents) {
5981
+ const latest = /* @__PURE__ */ new Map();
5982
+ for (const event of toolCallEvents ?? []) {
5983
+ const action = ACTIONS.find((a) => event.name?.endsWith(a.tool));
5984
+ const output = event.output ?? "";
5985
+ if (!action || !output || hasEncryptedToolPayload(event) || NOT_AN_ATTEMPT.test(output))
5986
+ continue;
5987
+ const kept = latest.get(action.kind)?.status;
5988
+ if (kept === "made") continue;
5989
+ const receipt = toReceipt(action, event.arguments ?? "", output);
5990
+ if (kept === "unknown" && receipt.status !== "made") continue;
5991
+ latest.set(action.kind, receipt);
5992
+ }
5993
+ return [...latest.values()];
5994
+ }
5995
+ function toReceipt(action, argumentsJson, output) {
5996
+ const args = parseObject(argumentsJson);
5997
+ const result = parseObject(output);
5998
+ return {
5999
+ kind: action.kind,
6000
+ venueName: text2(args.venue_name) ?? "",
6001
+ day: text2(args.day) ?? "",
6002
+ time: text2(args.time) ?? "",
6003
+ partySize: count(args.party_size),
6004
+ reservationId: text2(result.reservation_id) ?? text2(args.reservation_id),
6005
+ resyUrl: text2(result.reservations_url),
6006
+ feeSummary: text2(result.fee_summary),
6007
+ ...outcome(action, output, result)
6008
+ };
6009
+ }
6010
+ function outcome(action, output, result) {
6011
+ if (action.alreadyDone.test(output)) return { status: "already_done" };
6012
+ const reason = text2(result.error);
6013
+ if (action.kind === "booking") {
6014
+ if (BOOKING_OUTCOME_UNKNOWN.test(output)) return { status: "unknown" };
6015
+ const held = result.success === true || text2(result.reservation_id) !== void 0;
6016
+ return held ? { status: "made" } : { status: "not_made", reason };
6017
+ }
6018
+ if (result.error_code === CANCEL_OUTCOME_UNKNOWN) return { status: "unknown", reason };
6019
+ const cancelled = result.success === true && !result.error && !result.error_code;
6020
+ return cancelled ? { status: "made" } : { status: "not_made", reason };
6021
+ }
6022
+ function parseObject(json5) {
6023
+ try {
6024
+ const parsed = JSON.parse(json5);
6025
+ return parsed && typeof parsed === "object" ? parsed : {};
6026
+ } catch {
6027
+ return {};
6028
+ }
6029
+ }
6030
+ function text2(value) {
6031
+ if (typeof value === "number") return String(value);
6032
+ return typeof value === "string" && value ? value : void 0;
6033
+ }
6034
+ function count(value) {
6035
+ const n = typeof value === "string" && value.trim() ? Number(value) : value;
6036
+ return typeof n === "number" && Number.isFinite(n) ? n : void 0;
6037
+ }
6038
+
6039
+ // src/tools/confirmConstants.ts
6040
+ var CONFIRM_TOOL_NAME = "prompt_user_confirm";
6041
+ var BOOK_RESTAURANT = {
6042
+ tool: "AnumaPaymentsMCP-anuma_book_restaurant",
6043
+ status: "confirmed_not_booked",
6044
+ fields: ["venue_id", "config_id", "party_size", "day", "venue_name", "time"]
6045
+ };
6046
+ var CANCEL_RESERVATION = {
6047
+ tool: "AnumaPaymentsMCP-anuma_cancel_reservation",
6048
+ status: "confirmed_not_cancelled",
6049
+ fields: [
6050
+ "reservation_id",
6051
+ "venue_name",
6052
+ "day",
6053
+ "time",
6054
+ "party_size",
6055
+ "fee_applies",
6056
+ "fee_amount"
6057
+ ]
6058
+ };
6059
+ var CONFIRM_REQUIRED_FIELDS = /* @__PURE__ */ new Map([
6060
+ ["book_restaurant", BOOK_RESTAURANT],
6061
+ ["anuma_book_restaurant", BOOK_RESTAURANT],
6062
+ ["anumapaymentsmcp-anuma_book_restaurant", BOOK_RESTAURANT],
6063
+ ["cancel_reservation", CANCEL_RESERVATION],
6064
+ ["anuma_cancel_reservation", CANCEL_RESERVATION],
6065
+ ["anumapaymentsmcp-anuma_cancel_reservation", CANCEL_RESERVATION]
6066
+ ]);
6067
+ function confirmRequirement(action) {
6068
+ if (typeof action !== "string") return void 0;
6069
+ return CONFIRM_REQUIRED_FIELDS.get(action.trim().toLowerCase());
6070
+ }
6071
+ function missingConfirmFields(action, parameters) {
6072
+ const requirement = confirmRequirement(action);
6073
+ if (!requirement) return [];
6074
+ const shown = /* @__PURE__ */ new Set();
6075
+ for (const parameter of Array.isArray(parameters) ? parameters : []) {
6076
+ const { name, value } = parameter ?? {};
6077
+ if (typeof name === "string" && typeof value === "string" && value.trim()) shown.add(name);
6078
+ }
6079
+ return requirement.fields.filter((field10) => !shown.has(field10));
6080
+ }
6081
+
6082
+ // src/tools/walletPaymentConstants.ts
6083
+ var SELECT_PAYMENT_WALLET_TOOL_NAME = "select_payment_wallet";
6084
+ var WALLET_PAYMENT_TOOL_NAME = "prompt_wallet_payment";
6085
+ var FLIGHT_BOOK_TOOL_SET = "flight-book";
6086
+ var FLIGHT_SERVER_TOOL_NAMES = [
6087
+ "AnumaPaymentsMCP-anuma_search_flights",
6088
+ "AnumaPaymentsMCP-anuma_flight_offer_details",
6089
+ "AnumaPaymentsMCP-anuma_create_flight_intent",
6090
+ "AnumaPaymentsMCP-anuma_get_flight_booking"
6091
+ ];
6092
+
6093
+ // src/lib/storage/mcpImages.ts
6094
+ var IMAGE_TOOL_NAMES = /* @__PURE__ */ new Set([
6095
+ "AnumaMediaMCP-anuma_create_image",
6096
+ "anuma_create_image",
6097
+ "AnumaImageMCP-generate_cloud_image",
6098
+ "AnumaImageMCP-edit_cloud_image",
6099
+ "generate_cloud_image",
6100
+ "edit_cloud_image"
6101
+ ]);
6102
+ function toolOutputForModel(name, output) {
6103
+ if (!name || !IMAGE_TOOL_NAMES.has(name)) return output;
6104
+ try {
6105
+ const {
6106
+ imageUrl: _imageUrl,
6107
+ url: _url,
6108
+ output_images: _outputImages,
6109
+ ...rest
6110
+ } = JSON.parse(output);
6111
+ return JSON.stringify(rest);
6112
+ } catch {
6113
+ return output;
6114
+ }
6115
+ }
6116
+ var VIDEO_EXTENSIONS = ["mp4", "webm", "mov"];
6117
+ var VIDEO_EXTENSION_RE = new RegExp(`\\.(${VIDEO_EXTENSIONS.join("|")})(?:[?#]|$)`, "i");
6118
+
6119
+ // src/tools/appBuilderPrompt.ts
6120
+ var APP_BUILDER_PROMPT = `App Builder tools (create_file, patch_file, and the rest described below) are available this turn, but they are OPTIONAL \u2014 having them does not mean you should build anything.
6121
+
6122
+ Build ONLY when the user gives an explicit instruction to build, create, generate, or change an app, tool, or UI \u2014 e.g. "build a todo app", "make a snake game", "add a footer to the app", "change the background to blue". A bare app name or feature reference is NOT a build instruction: if the user just says "todo app", "a snake game", or "qr code", or asks a question about an app, do NOT generate anything \u2014 reply normally, or ask what they'd like you to build. The same holds for greetings, questions, stories, explanations, advice, or code that isn't a runnable app: respond normally and do not create files. When it's unclear whether the user actually wants an app built, ask instead of building.
6123
+
6124
+ Once the user HAS given an explicit build instruction, you are in App Builder mode: you produce polished, production-quality React apps that feel designed \u2014 not generic.
6125
+
6126
+ DESIGN: on the initial build, before writing code, briefly state (2-3 sentences) what you want this app to feel like \u2014 name the specific fonts, the palette in hex, and one signature detail. Be concrete: "Fraunces display + Inter body + JetBrains Mono metadata, terracotta #b75432 accent on bone #f3efe6, paper-grain texture via inline SVG noise" is the kind of specificity that anchors the build. Default to nothing: don't pick "safe modern productivity" unless the brief literally asks for utilitarian. On later turns, state a design brief only when the change is visual (a restyle, new theme, or new UI); for a text, logic, data, or behavior-only change, skip the brief and just make the change.
6127
+
6128
+ EDIT SCOPE: Change only the files and lines required by the current user request. A title rename is a text edit, not a design change. Preserve styles, dependencies, and unrelated logic on text edits. Design feedback does not authorize unrelated edits.
6129
+
6130
+ WORKFLOW:
6131
+ 1. NEVER output code as text or markdown. ALWAYS use tools.
6132
+ 2. Initial build: state your design brief, then create_file with a "files" array writing ALL files in one call. The preview renders automatically.
6133
+ 3. patch_file requires read_file for that path earlier in the conversation (or create_file in the same conversation, which counts). read_file output is numbered "42: <text>" \u2014 the prefix is display-only; never copy it into "find". Each "find" must match exactly one location \u2014 include 2-3 lines of surrounding context. Pass multiple patches per call to change distinct locations.
6134
+ 4. Prefer patch_file for changes to existing files \u2014 surgical, reviewable, preserves surrounding code. Reserve create_file overwrites for substantial restructuring. Inserts: find the line before, replace with that line + the new line. Deletes: empty replace.
6135
+ 5. On the initial build, when adding new UI, or after user-requested design changes, call critique_design first and then audit_design. Critique checks taste, hierarchy, and distinctiveness. Audit checks tokens, focus states, aria-labels, alt text, and heading order. Patch only issues within the current user request. For text, logic, or data edits, skip both design tools.
6136
+ 6. Before declaring done, call verify_app. The host's preview runtime reports whether your app actually mounted and surfaces any errors it captured \u2014 hallucinated imports, syntax the bundler rejected, undefined components, runtime crashes. If \`rendered\` is false or \`errors\` is non-empty, fix every error before the next message. If the response includes a \`note\` that verify_app isn't wired in this host, skip runtime verification. An unavailable runtime verifier does not authorize design changes. Use audit_design and critique_design only for the initial build, adding new UI, or user-requested design changes.
6137
+ 7. Keep responses to a sentence or two. Exception: when responding to critique_design, write a substantive paragraph per rubric question.
6138
+
6139
+ STRUCTURE:
6140
+ - App.js: default-export React component. For non-trivial apps, define small co-located helpers (Header, CardList, Card, EditDialog, \u2026) above the default export rather than one giant App.
6141
+ - App.css: all styles, imported in App.js. No inline style objects.
6142
+ - package.json: list ALL imported packages including react. No CDN script tags. Versions are auto-pinned.
6143
+ - Do NOT create index.js or index.html (auto-generated).
6144
+
6145
+ STATE & PERSISTENCE:
6146
+ - The code runs in the user's browser \u2014 standard browser APIs available (localStorage, URL hash, Date, fetch).
6147
+ - Persist user-created data (todos, cards, notes, settings) with localStorage so it survives refresh. Use a versioned key ("kanban.v1") and JSON.parse inside try/catch on read.
6148
+ - Treat persisted data as untrusted and possibly stale: a saved record may predate a field you just added (a timestamp, id, or setting), so default-fill or skip missing fields on read \u2014 never assume a loaded record has every field the current code expects. Bump the key version when the shape changes.
6149
+ - Lift state to App when multiple children need it; keep it local otherwise. Extract non-trivial logic into custom hooks (useTodos, useTimer).
6150
+ - useReducer for state shapes with several interrelated fields (cards + columns + filters + drag state).
6151
+
6152
+ FILE & IMAGE UPLOAD:
6153
+ - Picker: \`<input type="file" accept="image/*" multiple>\` (vary accept by use case: \`.csv,text/csv\`, \`.txt,.md,text/plain\`, etc.).
6154
+ - Drag-and-drop: call \`e.preventDefault()\` in onDragOver (otherwise drop never fires); read files from \`event.dataTransfer.files\`. Drop and picker should coexist.
6155
+ - Previews: \`<img src={URL.createObjectURL(file)} />\`. Call \`URL.revokeObjectURL\` on unmount or before replacing \u2014 otherwise the browser leaks memory.
6156
+ - localStorage cannot hold large binary. Persist only metadata for big files; re-upload on next visit (or use IndexedDB if you really need durable binary storage).
6157
+ - Validate size and type before processing; reject files larger than the app can hold in memory.
6158
+
6159
+ AI CAPABILITIES \u2014 \`window.app.complete(prompt: string): Promise<string>\` is available in this environment. Build for the real call.
6160
+ - When the app's core purpose is conversation, generation, grading, recommendation, or any natural-language task, this IS the implementation \u2014 call it. NEVER fake it: no hardcoded or canned replies, no random or \`setTimeout\` "thinking", no demo-mode stub. A chat app, tutor, or assistant that doesn't call \`window.app.complete\` is wrong, not a placeholder.
6161
+ - Don't force it where it isn't needed \u2014 a calculator, todo list, or timer has no reason to call it. Match the implementation to what the app actually does.
6162
+ - Async: wrap in try/catch, show a loading state, and disable dependent inputs while waiting. Surface failures gracefully ("Couldn't reach the AI \u2014 try again").
6163
+ - Structure prompts with clear sections (Role / Context / Task / Format) and pass the relevant state (e.g. the running conversation) as context. Render the response as untrusted text: \`white-space: pre-wrap\`, never dangerouslySetInnerHTML.
6164
+ - Skip the call only when the answer is genuinely static \u2014 no language or reasoning needed. Cache repeated prompts in component state when sensible.
6165
+
6166
+ CODE QUALITY \u2014 write as a senior engineer would:
6167
+ - Handle edge cases (empty inputs, division by zero, invalid data). Show helpful error states, not crashes.
6168
+ - Never let formatting throw during render: guard \`new Date(x)\` / \`Intl\` / \`toLocaleString\` / \`.toFixed\` against missing or invalid values (e.g. \`Number.isNaN(d.getTime())\`) and return a fallback \u2014 one bad value from storage or state must not blank the whole app.
6169
+ - Semantic HTML: real headings, labels, buttons (not divs with onClick), form elements.
6170
+ - Accessible: aria-labels on icon buttons, focus-visible styles, keyboard navigable.
6171
+ - Extract into custom hooks or helpers when a component grows past ~80 lines.
6172
+
6173
+ STYLING \u2014 Tailwind is the CHASSIS, custom CSS carries the IDENTITY:
6174
+ - Tailwind (via Play CDN) for layout, spacing, state (hover/focus), responsive prefixes \u2014 uniform structure.
6175
+ - App.css carries the identity: @import the Google Fonts, declare CSS custom properties (--bg, --panel, --card, --ink, --muted, --accent, --hairline, --radius, --radius-sm, --shadow-card, --shadow-card-hi) in :root, gradients/textures/signature animations Tailwind can't express. Reference tokens from Tailwind via arbitrary values (\`className="bg-[var(--bg)]"\`) when needed. A well-designed app has 60-200 lines of App.css \u2014 not zero.
6176
+ - Modern CSS is how apps stop reading as generic-AI-output: \`color-mix(in oklab, var(--accent) 14%, transparent)\` for tinting, \`oklch()\` / \`oklab()\` for perceptually-uniform color, \`:has()\`, \`conic-gradient\` / \`radial-gradient\`, \`backdrop-filter\`, \`@container\` queries, inline SVG \`feTurbulence\` for texture.
6177
+ - Don't pull additional CSS frameworks. Tailwind + custom CSS is enough.
6178
+
6179
+ VISUAL DESIGN \u2014 every app should look like a real product, not a wireframe:
6180
+ - Let the chosen aesthetic drive every decision. "Editorial" wants italic display type and restrained color; "retro arcade" wants chunky shadows and saturated gradients. No safe-modern defaults.
6181
+ - Atmospheric depth: subtle radial wash, paper grain (inline SVG noise), warm/cool tint \u2014 every canvas should feel like a designed space, not a #fff rectangle.
6182
+ - Three shadow tiers (rest / hover / active). Pair drop shadows with inset highlights (\`box-shadow: 0 1px 0 rgba(255,255,255,.9) inset, 0 6px 14px -10px ...\`) for tactile feel.
6183
+ - Typography hierarchy: display font for headlines (italic accents on key words via \`<em>\`), body font for content, mono for metadata/labels \u2014 different families telegraph different information types.
6184
+ - Interactive polish: hover/active/focus on every clickable element, 200-300ms cubic-bezier transitions, keyboard shortcuts where natural (Esc to cancel, Cmd/Ctrl+Enter to commit), focus-visible rings keyed to the accent.
6185
+ - Light/dark mode if reasonable: a single CSS variable inversion under \`[data-theme="dark"]\`.
6186
+ - Customization affordance for multi-session apps: small "Tweaks" panel with theme / density / accent. Skip for throwaway demos.
6187
+ - Responsive: Tailwind responsive prefixes (\`sm:\`, \`md:\`, \`lg:\`) and \`@media\` in App.css. Mobile-first.`;
6188
+
6189
+ // src/tools/document/documentBuilderPrompt.ts
6190
+ var DOCUMENT_BUILDER_PROMPT = `Document tools (create_document, read_document, patch_document) are available this turn, but they are OPTIONAL \u2014 having them does not mean you should produce a document.
6191
+
6192
+ Use them ONLY when the user explicitly asks you to draft, write, compose, or generate a document \u2014 a contract, agreement, NDA, letter, cover letter, memo, essay, report, brief, proposal, or resume \u2014 e.g. "draft me a service agreement", "write a cover letter for this job", "make a one-page memo about X". A bare topic, a question, a request for an explanation, or a request for a slide deck or an interactive app is NOT a document instruction: respond normally and do not call these tools. When it's unclear whether the user wants a finished document, ask instead.
6193
+
6194
+ When you DO author a document, the document body goes entirely into the tool \u2014 NEVER write the document text as chat prose. The tool renders your source to a real, paginated PDF and attaches it to your reply automatically. AFTER the tool returns, briefly confirm in ONE short sentence what you made (e.g. "Drafted a cover letter for the product manager role \u2014 see the attached PDF."). Do not restate the document's contents.
6195
+
6196
+ AUTHORING \u2014 write the "source" as a single @react-pdf/renderer JSX expression (this is react-pdf, NOT HTML and NOT a slide deck):
6197
+ - Root is exactly one <Document> with at least one <Page>. Its only children are <Page> elements. Add more <Page> elements for longer documents \u2014 react-pdf also paginates overflow automatically.
6198
+ - Inside a <Page>: use <View> as flex containers and <Text> for ALL text. Raw text is only allowed inside <Text> (and <Link>); a <View> or <Page> may not contain bare text.
6199
+ - <Text> may nest <Text>, <Link>, or <Note> for inline styling, hyperlinks, and (rarely) a PDF comment annotation. A <Link> src must be an ordinary web link \u2014 http(s), mailto, tel, or a relative/anchor link; javascript:/data:/file: links are rejected. <Image> is self-closing and its src MUST be an inline "data:" URI \u2014 remote image URLs are rejected for privacy. Vector primitives (<Rect>/<Circle>/<Path>/etc.) must live inside an <Svg>.
6200
+ - Styling is react-pdf style objects only: style={{ marginTop: 12, fontSize: 11, fontFamily: "Times-Roman", flexDirection: "column" }}. Flexbox only \u2014 there is NO CSS grid, float, or box-shadow. Use camelCase react-pdf keys (margin/padding, flex*, width/height, color/backgroundColor, border*, text*, etc.).
6201
+ - fontFamily must be a built-in PDF font: Helvetica, Times-Roman, or Courier (with -Bold / -Oblique / -Italic / -BoldOblique variants), or Symbol / ZapfDingbats. Custom fonts are not supported.
6202
+ - LITERAL VALUES ONLY: no variables, no JS expressions, no .map(), no event handlers (onClick, \u2026), no spreads. Every attribute is a literal string, number, boolean, or a style object of literals.
6203
+
6204
+ A minimal example:
6205
+
6206
+ <Document>
6207
+ <Page size="A4" style={{ paddingVertical: 56, paddingHorizontal: 64, fontFamily: "Times-Roman", fontSize: 11, lineHeight: 1.5 }}>
6208
+ <Text style={{ fontSize: 18, fontFamily: "Times-Bold", marginBottom: 16, textAlign: "center" }}>MUTUAL NON-DISCLOSURE AGREEMENT</Text>
6209
+ <Text style={{ marginBottom: 10 }}>This Mutual Non-Disclosure Agreement (the "Agreement") is entered into as of the date of last signature below.</Text>
6210
+ <View style={{ marginTop: 14 }}>
6211
+ <Text style={{ fontFamily: "Times-Bold", marginBottom: 4 }}>1. Confidential Information</Text>
6212
+ <Text>Each party may disclose certain confidential and proprietary information to the other party.</Text>
6213
+ </View>
6214
+ </Page>
6215
+ </Document>
6216
+
6217
+ WORKFLOW:
6218
+ 1. To create or fully restructure a document, call create_document with the complete source.
6219
+ 2. To revise an existing document, call read_document first (its output is line-numbered "42: <text>" \u2014 the prefix is display-only, never copy it into a "find"), then patch_document with small find/replace edits. Each "find" must match exactly one location; add 2-3 lines of surrounding context if needed. Prefer patching over rewriting.
6220
+ 3. To attach several separate documents in one reply, call create_document multiple times with distinct documentId slugs (e.g. "nda", "cover-letter"). Each renders to its own PDF.
6221
+ 4. If a tool reports a DSL error (with line:col) or a render failure, fix the source and retry \u2014 do not give up or fall back to writing the document as chat text.`;
6222
+
6223
+ // src/lib/tools/serverTools.ts
6224
+ var DEFAULT_CACHE_EXPIRATION_MS = 24 * 60 * 60 * 1e3;
6225
+ var SERVER_TOOLS_CACHE_KEY = "sdk_server_tools_cache";
6226
+ var CACHE_VERSION = "1.3";
6227
+ var MIN_CONTENT_LENGTH_FOR_TOOLS = 5;
6228
+ var MAX_CLIENT_TOOLS_AFTER_FILTER = 10;
6229
+ var CLIENT_TOOLS_MIN_SIMILARITY = 0.53;
6230
+ var CLIENT_TOOLS_RELEVANCE_RATIO = 0.75;
6231
+ function isNewToolFormat(tool) {
6232
+ return "schema" in tool && tool.schema !== void 0;
6233
+ }
6234
+ function isNewResponseFormat(response) {
6235
+ return "checksum" in response && "tools" in response && typeof response.checksum === "string";
6236
+ }
6237
+ function convertServerToolsResponse(response) {
6238
+ let toolsMap;
6239
+ let checksum;
6240
+ if (isNewResponseFormat(response)) {
6241
+ toolsMap = response.tools;
6242
+ checksum = response.checksum;
6243
+ } else {
6244
+ toolsMap = response;
6245
+ }
6246
+ const tools = Object.values(toolsMap).map((tool) => {
6247
+ if (isNewToolFormat(tool)) {
6248
+ return {
6249
+ type: "function",
6250
+ name: tool.schema.name,
6251
+ description: tool.schema.description,
6252
+ parameters: tool.schema.parameters,
6253
+ ...tool.embedding && { embedding: tool.embedding }
6254
+ };
6255
+ }
6256
+ return {
6257
+ type: "function",
6258
+ name: tool.name,
6259
+ description: tool.description,
6260
+ parameters: tool.parameters
6261
+ };
6262
+ });
6263
+ return { tools, checksum };
6264
+ }
6265
+ function toCompletionsFormat(tool) {
6266
+ return {
6267
+ type: "function",
6268
+ function: {
6269
+ name: tool.name,
6270
+ description: tool.description,
6271
+ parameters: tool.parameters
6272
+ }
6273
+ };
6274
+ }
6275
+ function toResponsesFormat(tool) {
6276
+ return {
6277
+ type: "function",
6278
+ name: tool.name,
6279
+ description: tool.description,
6280
+ parameters: tool.parameters
6281
+ };
6282
+ }
6283
+ function getCachedServerTools() {
6284
+ if (typeof localStorage === "undefined") return null;
6285
+ try {
6286
+ const cached = localStorage.getItem(SERVER_TOOLS_CACHE_KEY);
6287
+ if (!cached) return null;
6288
+ const parsed = JSON.parse(cached);
6289
+ if (parsed.version !== CACHE_VERSION) {
6290
+ removeLocalStorageCache();
6291
+ return null;
6292
+ }
6293
+ return parsed;
6294
+ } catch {
6295
+ return null;
6296
+ }
6297
+ }
6298
+ function isCacheExpired(cache, expirationMs = DEFAULT_CACHE_EXPIRATION_MS) {
6299
+ if (!cache) return true;
6300
+ return Date.now() - cache.timestamp > expirationMs;
6301
+ }
6302
+ function buildCacheEntry(tools, checksum, etag) {
6303
+ return {
6304
+ tools,
6305
+ timestamp: Date.now(),
6306
+ version: CACHE_VERSION,
6307
+ ...checksum && { checksum },
6308
+ ...etag && { etag }
6309
+ };
6310
+ }
6311
+ function writeLocalStorageCache(entry) {
6312
+ if (typeof localStorage === "undefined") return;
6313
+ try {
6314
+ localStorage.setItem(SERVER_TOOLS_CACHE_KEY, JSON.stringify(entry));
6315
+ } catch (error) {
6316
+ getLogger().warn("[serverTools] Failed to cache tools:", error);
6317
+ }
6318
+ }
6319
+ function removeLocalStorageCache() {
6320
+ if (typeof localStorage === "undefined") return;
6321
+ localStorage.removeItem(SERVER_TOOLS_CACHE_KEY);
6322
+ }
6323
+ var localStorageToolsCache = {
6324
+ get: getCachedServerTools,
6325
+ set: writeLocalStorageCache,
6326
+ clear: removeLocalStorageCache
6327
+ };
6328
+ function clearServerToolsCache(cache = localStorageToolsCache) {
6329
+ return cache.clear?.();
6330
+ }
6331
+ function getToolsChecksum2(cache = localStorageToolsCache) {
6332
+ const cached = cache.get();
6333
+ return cached instanceof Promise ? cached.then((c) => c?.checksum) : cached?.checksum;
6334
+ }
6335
+ function shouldRefreshTools(responseChecksum, cache = localStorageToolsCache) {
6336
+ if (!responseChecksum) {
6337
+ return false;
6338
+ }
6339
+ const decide = (cachedChecksum2) => !cachedChecksum2 || cachedChecksum2 !== responseChecksum;
5467
6340
  const cachedChecksum = getToolsChecksum2(cache);
5468
6341
  return cachedChecksum instanceof Promise ? cachedChecksum.then(decide) : decide(cachedChecksum);
5469
6342
  }
@@ -6438,6 +7311,13 @@ function createErrorResult(message, onError) {
6438
7311
  function isAbortError(err) {
6439
7312
  return err instanceof Error && err.name === "AbortError";
6440
7313
  }
7314
+ var ProviderStreamError = class extends Error {
7315
+ constructor(message, code) {
7316
+ super(message);
7317
+ this.name = "ProviderStreamError";
7318
+ this.code = code;
7319
+ }
7320
+ };
6441
7321
  function getInStreamErrorMessage(chunk) {
6442
7322
  if (!chunk || typeof chunk !== "object") return null;
6443
7323
  const obj = chunk;
@@ -6724,7 +7604,7 @@ var CompletionsStrategy = class {
6724
7604
  processStreamChunk(chunk, accumulator) {
6725
7605
  const result = { content: null, thinking: null };
6726
7606
  const inStreamErr = getInStreamErrorMessage(chunk);
6727
- if (inStreamErr) throw new Error(inStreamErr);
7607
+ if (inStreamErr) throw new ProviderStreamError(inStreamErr);
6728
7608
  const rawChunk = chunk;
6729
7609
  const typedChunk = rawChunk.response && rawChunk.type === "response" ? rawChunk.response : chunk;
6730
7610
  if (typedChunk.id && !accumulator.responseId) {
@@ -7575,7 +8455,7 @@ var ResponsesStrategy = class {
7575
8455
  const result = { content: null, thinking: null };
7576
8456
  const typedChunk = chunk;
7577
8457
  const inStreamErr = getInStreamErrorMessage(chunk);
7578
- if (inStreamErr) throw new Error(inStreamErr);
8458
+ if (inStreamErr) throw new ProviderStreamError(inStreamErr);
7579
8459
  if (typedChunk.type === "response.failed") {
7580
8460
  const resp = typedChunk.response;
7581
8461
  const err = resp?.error;
@@ -7592,9 +8472,12 @@ var ResponsesStrategy = class {
7592
8472
  }
7593
8473
  }
7594
8474
  const label = code ? `[${code}] ` : "";
7595
- throw new Error(`${label}${message || "Upstream request failed"}`);
8475
+ throw new ProviderStreamError(
8476
+ `${label}${message || "Upstream request failed"}`,
8477
+ code || void 0
8478
+ );
7596
8479
  }
7597
- throw new Error("Upstream request failed (response.failed)");
8480
+ throw new ProviderStreamError("Upstream request failed (response.failed)");
7598
8481
  }
7599
8482
  if (typedChunk.type === "response" && typedChunk.response) {
7600
8483
  const resp = typedChunk.response;
@@ -8218,13 +9101,6 @@ function isRetriableStreamError(err) {
8218
9101
  if (msg.includes("connection refused") || msg.includes("connect error")) return true;
8219
9102
  return false;
8220
9103
  }
8221
- var ProviderStreamError = class extends Error {
8222
- constructor(message, code) {
8223
- super(message);
8224
- this.name = "ProviderStreamError";
8225
- this.code = code;
8226
- }
8227
- };
8228
9104
  function extractProviderStreamError(chunk) {
8229
9105
  if (!chunk || typeof chunk !== "object") return null;
8230
9106
  const errField = chunk.error;
@@ -8605,6 +9481,7 @@ async function runToolLoop(options) {
8605
9481
  thinking: redactor.deAnonymize(acc.thinking)
8606
9482
  }) : strategy.buildFinalResponse(acc);
8607
9483
  const finalContentText = (acc) => redactor ? redactor.deAnonymize(acc.content) : acc.content;
9484
+ let streamingAccumulator = null;
8608
9485
  try {
8609
9486
  let sseError = null;
8610
9487
  let apiTools = toolsToApiFormat(tools, resolved);
@@ -8756,6 +9633,7 @@ async function runToolLoop(options) {
8756
9633
  }
8757
9634
  });
8758
9635
  accumulator = createStreamAccumulator(model || void 0);
9636
+ streamingAccumulator = accumulator;
8759
9637
  contentSmoother = new StreamSmoother((text16) => {
8760
9638
  if (emitData) emitData(text16);
8761
9639
  }, smoothing);
@@ -9266,6 +10144,7 @@ Executing tool: ${toolInfo}
9266
10144
  }
9267
10145
  });
9268
10146
  currentAccumulator = createStreamAccumulator(model || void 0);
10147
+ streamingAccumulator = currentAccumulator;
9269
10148
  contContentSmoother = new StreamSmoother((text16) => {
9270
10149
  if (emitData) emitData(text16);
9271
10150
  }, smoothing);
@@ -9463,7 +10342,7 @@ Executing tool: ${toolInfo}
9463
10342
  const statusCode = err instanceof Error && "statusCode" in err ? err.statusCode : void 0;
9464
10343
  const connectionLost = !(err instanceof ProviderStreamError) && !(err instanceof SseError);
9465
10344
  return {
9466
- data: null,
10345
+ data: connectionLost && streamingAccumulator ? buildResponseFinal(streamingAccumulator) : null,
9467
10346
  error: errorMsg,
9468
10347
  statusCode,
9469
10348
  ...connectionLost ? { connectionLost: true } : {}
@@ -9480,6 +10359,33 @@ function streamReplayPath(inferenceId) {
9480
10359
  function streamCancelPath(inferenceId) {
9481
10360
  return `${streamReplayPath(inferenceId)}/cancel`;
9482
10361
  }
10362
+ var ACTIVE_STREAMS_PATH = "/api/v1/chat/streams/active";
10363
+ function toActiveStream(value) {
10364
+ if (!value || typeof value !== "object") return null;
10365
+ const v = value;
10366
+ const text16 = (key3) => typeof v[key3] === "string" && v[key3] ? v[key3] : null;
10367
+ const inferenceId = text16("inference_id");
10368
+ if (!inferenceId || v.api_type !== "responses" && v.api_type !== "completions") return null;
10369
+ return {
10370
+ inferenceId,
10371
+ assistantMessageId: text16("assistant_message_id"),
10372
+ conversationId: text16("conversation_id"),
10373
+ parentMessageId: text16("parent_message_id"),
10374
+ apiType: v.api_type,
10375
+ model: text16("model") ?? void 0,
10376
+ startedAt: text16("started_at") ?? ""
10377
+ };
10378
+ }
10379
+ async function listActiveStreams(options) {
10380
+ const query = options.conversationId ? `?conversation_id=${encodeURIComponent(options.conversationId)}` : "";
10381
+ const response = await (options.fetch ?? globalThis.fetch)(
10382
+ `${options.baseUrl ?? BASE_URL}${ACTIVE_STREAMS_PATH}${query}`,
10383
+ { headers: { Authorization: `Bearer ${options.token}` } }
10384
+ );
10385
+ if (!response.ok) throw new Error(`Active streams request failed (${response.status})`);
10386
+ const body = await response.json();
10387
+ return Array.isArray(body.streams) ? body.streams.map(toActiveStream).filter((s) => s !== null) : [];
10388
+ }
9483
10389
  var StreamExpiredError = class extends Error {
9484
10390
  constructor(inferenceId, message) {
9485
10391
  super(message ?? `The resumable stream "${inferenceId}" has expired or no longer exists`);
@@ -9901,1378 +10807,654 @@ var xhrTransport = (options) => {
9901
10807
  push({ type: "done" });
9902
10808
  finished = true;
9903
10809
  };
9904
- xhr.onabort = () => {
9905
- options.signal?.removeEventListener("abort", abortHandler);
9906
- push({ type: "error", error: makeAbortError() });
9907
- finished = true;
9908
- };
9909
- xhr.send(options.body !== void 0 ? JSON.stringify(options.body) : void 0);
9910
- }
9911
- async function* createStream() {
9912
- if (options.signal?.aborted) throw makeAbortError();
9913
- while (true) {
9914
- await waitForItem();
9915
- while (queue.length > 0) {
9916
- const item = queue.shift();
9917
- if (item.type === "done") return;
9918
- if (item.type === "error") throw item.error;
9919
- yield item.value;
9920
- }
9921
- }
9922
- }
9923
- return { stream: createStream() };
9924
- };
9925
-
9926
- // src/lib/chatStore/types.ts
9927
- var ChatStoreError = class extends Error {
9928
- constructor(message, status, code, retryAfterMs) {
9929
- super(message);
9930
- this.status = status;
9931
- this.code = code;
9932
- this.retryAfterMs = retryAfterMs;
9933
- this.name = "ChatStoreError";
9934
- }
9935
- };
9936
-
9937
- // src/lib/db/conversationMemory/operations.ts
9938
- import { Q as Q3 } from "@nozbe/watermelondb";
9939
- var MAX_PER_CONVERSATION = 200;
9940
- function conversationMemoryToStored(row) {
9941
- return {
9942
- uniqueId: row.id,
9943
- conversationId: row.conversationId,
9944
- memoryId: row.memoryId,
9945
- score: row.score,
9946
- createdAt: row.createdAt
9947
- };
9948
- }
9949
- async function addConversationMemoriesOp(ctx, conversationId, items) {
9950
- if (!conversationId || items.length === 0) return;
9951
- const now = Date.now();
9952
- await ctx.database.write(async () => {
9953
- const existing = await ctx.conversationMemoryCollection.query(Q3.where("conversation_id", conversationId), Q3.sortBy("created_at", Q3.asc)).fetch();
9954
- const seen = new Set(existing.map((r) => r.memoryId));
9955
- const batchSeen = /* @__PURE__ */ new Set();
9956
- const deduped = [];
9957
- for (const it of items) {
9958
- if (!it.memoryId || seen.has(it.memoryId) || batchSeen.has(it.memoryId)) continue;
9959
- batchSeen.add(it.memoryId);
9960
- deduped.push(it);
9961
- }
9962
- if (deduped.length === 0) return;
9963
- const keptNew = deduped.length > MAX_PER_CONVERSATION ? deduped.slice(deduped.length - MAX_PER_CONVERSATION) : deduped;
9964
- const keepExistingCount = MAX_PER_CONVERSATION - keptNew.length;
9965
- const pruneCount = Math.max(0, existing.length - keepExistingCount);
9966
- const toPrune = existing.slice(0, pruneCount);
9967
- const ops = [
9968
- ...toPrune.map((r) => r.prepareDestroyPermanently()),
9969
- ...keptNew.map(
9970
- (it, i) => ctx.conversationMemoryCollection.prepareCreate((r) => {
9971
- r._setRaw("conversation_id", conversationId);
9972
- r._setRaw("memory_id", it.memoryId);
9973
- r._setRaw("score", it.score);
9974
- r._setRaw("created_at", now + i);
9975
- })
9976
- )
9977
- ];
9978
- await ctx.database.batch(...ops);
9979
- });
9980
- }
9981
- async function recordConversationMemoriesOp(ctx, conversationId, items, now = Date.now()) {
9982
- const turn = /* @__PURE__ */ new Map();
9983
- items.forEach((item, i) => {
9984
- if (item.memoryId && !turn.has(item.memoryId))
9985
- turn.set(item.memoryId, { score: item.score, createdAt: now + i });
9986
- });
9987
- if (!conversationId || turn.size === 0) return;
9988
- const kept = new Map([...turn].slice(-MAX_PER_CONVERSATION));
9989
- await ctx.database.write(async () => {
9990
- const existing = await ctx.conversationMemoryCollection.query(Q3.where("conversation_id", conversationId), Q3.sortBy("created_at", Q3.asc)).fetch();
9991
- const earlier = existing.filter((r) => !turn.has(r.memoryId));
9992
- const linked = new Set(existing.map((r) => r.memoryId));
9993
- await ctx.database.batch(
9994
- ...earlier.slice(0, Math.max(0, earlier.length - (MAX_PER_CONVERSATION - kept.size))).map((r) => r.prepareDestroyPermanently()),
9995
- ...existing.filter((r) => turn.has(r.memoryId) && !kept.has(r.memoryId)).map((r) => r.prepareDestroyPermanently()),
9996
- ...existing.filter((r) => kept.has(r.memoryId)).map(
9997
- (r) => r.prepareUpdate((row) => {
9998
- row._setRaw("score", kept.get(r.memoryId).score);
9999
- row._setRaw("created_at", kept.get(r.memoryId).createdAt);
10000
- })
10001
- ),
10002
- ...[...kept].filter(([memoryId]) => !linked.has(memoryId)).map(
10003
- ([memoryId, link]) => ctx.conversationMemoryCollection.prepareCreate((row) => {
10004
- row._setRaw("conversation_id", conversationId);
10005
- row._setRaw("memory_id", memoryId);
10006
- row._setRaw("score", link.score);
10007
- row._setRaw("created_at", link.createdAt);
10008
- })
10009
- )
10010
- );
10011
- });
10012
- }
10013
- async function getConversationMemoriesOp(ctx, conversationId) {
10014
- if (!conversationId) return [];
10015
- const rows = await ctx.conversationMemoryCollection.query(Q3.where("conversation_id", conversationId), Q3.sortBy("created_at", Q3.asc)).fetch();
10016
- return rows.map(conversationMemoryToStored);
10017
- }
10018
- async function clearConversationMemoriesOp(ctx, conversationId) {
10019
- if (!conversationId) return;
10020
- await ctx.database.write(async () => {
10021
- const rows = await ctx.conversationMemoryCollection.query(Q3.where("conversation_id", conversationId)).fetch();
10022
- if (rows.length === 0) return;
10023
- await ctx.database.batch(...rows.map((r) => r.prepareDestroyPermanently()));
10024
- });
10025
- }
10026
-
10027
- // src/lib/chatStore/vaultRecords.ts
10028
- var PINNED_SORT_BASE = 2 ** 51;
10029
- var recordBudgetBytes = 104e4;
10030
- var storedDims = 1536;
10031
- var acceptedDims = /* @__PURE__ */ new Set([1536, 4096]);
10032
- var maxChunks = 16;
10033
- var droppable = ["thoughtProcess", "thinking", "sources", "toolCallEvents"];
10034
- var messageMetadata = /* @__PURE__ */ new Set(["conversationId", "uniqueId", "vector", "chunks"]);
10035
- var alwaysStored = /* @__PURE__ */ new Set(["role", "content", "responseDuration"]);
10036
- var maxBubbles = 3;
10037
- var maxTurnIdBytes = 128;
10038
- var maxBubbleBytes = 16384;
10039
- var anumaOrigins = /* @__PURE__ */ new Set(["intro", "reply"]);
10040
- function plain(value) {
10041
- return JSON.parse(JSON.stringify(value ?? {}));
10042
- }
10043
- function text3(value) {
10044
- return typeof value === "string" ? value : void 0;
10045
- }
10046
- function number(value) {
10047
- return typeof value === "number" ? value : void 0;
10048
- }
10049
- function readAnumaTurn(value) {
10050
- if (!isObject(value)) return void 0;
10051
- const { turnId, bubbles, origin } = value;
10052
- if (typeof turnId !== "string" || turnId.length === 0 || new TextEncoder().encode(turnId).length > maxTurnIdBytes)
10053
- return void 0;
10054
- if (bubbles !== void 0 && (!Array.isArray(bubbles) || bubbles.length < 1 || bubbles.length > maxBubbles || !bubbles.every((b) => typeof b === "string") || bubbles.reduce((sum, b) => sum + new TextEncoder().encode(b).length, 0) > maxBubbleBytes))
10055
- return void 0;
10056
- if (origin !== void 0 && (typeof origin !== "string" || !anumaOrigins.has(origin)))
10057
- return void 0;
10058
- return {
10059
- turnId,
10060
- ...bubbles !== void 0 ? { bubbles: [...bubbles] } : {},
10061
- ...origin !== void 0 ? { origin } : {}
10062
- };
10063
- }
10064
- function isObject(value) {
10065
- return value !== null && typeof value === "object" && !Array.isArray(value);
10066
- }
10067
- function conversationSortKey(data, createdAt) {
10068
- const pinnedAt = number(data.pinnedAt);
10069
- if (pinnedAt) return PINNED_SORT_BASE + pinnedAt;
10070
- return number(data.lastActivityAt) ?? createdAt;
10071
- }
10072
- var CONTEXT_FIELD_PREFIX = "context.";
10073
- function contextField(key3) {
10074
- return `${CONTEXT_FIELD_PREFIX}${key3}`;
10075
- }
10076
- function contextFields(context) {
10077
- return plain(
10078
- Object.fromEntries(Object.entries(context ?? {}).map(([k, value]) => [contextField(k), value]))
10079
- );
10080
- }
10081
- function conversationContext(data) {
10082
- const entries = Object.entries(data).filter(([field10]) => field10.startsWith(CONTEXT_FIELD_PREFIX)).map(([field10, value]) => [field10.slice(CONTEXT_FIELD_PREFIX.length), value]);
10083
- return entries.length > 0 ? plain(Object.fromEntries(entries)) : void 0;
10084
- }
10085
- function toConversation(record2) {
10086
- const pinnedAt = number(record2.data.pinnedAt);
10087
- const projectId = text3(record2.data.projectId);
10088
- const context = conversationContext(record2.data);
10089
- return {
10090
- uniqueId: record2.id,
10091
- conversationId: record2.id,
10092
- title: text3(record2.data.title) ?? "",
10093
- ...projectId ? { projectId } : {},
10094
- createdAt: new Date(record2.createdAt),
10095
- updatedAt: new Date(record2.updatedAt),
10096
- isDeleted: record2.isDeleted,
10097
- pinnedAt: pinnedAt ? new Date(pinnedAt) : null,
10098
- ...context ? { context } : {}
10099
- };
10100
- }
10101
- function anumaTurnData(value) {
10102
- if (value === void 0) return void 0;
10103
- const turn = readAnumaTurn(value);
10104
- if (!turn) throw new ChatStoreError("Invalid Anuma turn", 0, "invalid_record");
10105
- return turn;
10106
- }
10107
- function messageData(options) {
10108
- const anuma = anumaTurnData(options.anuma);
10109
- return plain({
10110
- ...Object.fromEntries(
10111
- Object.entries(options).filter(
10112
- ([key3, value]) => key3 !== "anuma" && !messageMetadata.has(key3) && (!!value || alwaysStored.has(key3))
10113
- )
10114
- ),
10115
- ...anuma ? { anuma } : {}
10116
- });
10117
- }
10118
- function toMessage(record2) {
10119
- const { chunkOffsets: _offsets, truncated: _truncated, anuma: rawAnuma, ...fields } = record2.data;
10120
- const anuma = readAnumaTurn(rawAnuma);
10121
- return {
10122
- ...anuma ? { anuma } : {},
10123
- ...fields,
10124
- uniqueId: record2.id,
10125
- messageId: record2.sortKey,
10126
- conversationId: record2.conversationId,
10127
- createdAt: new Date(record2.createdAt),
10128
- updatedAt: new Date(record2.updatedAt)
10129
- };
10130
- }
10131
- function chunkOffsets(record2) {
10132
- const offsets = record2.data.chunkOffsets;
10133
- return Array.isArray(offsets) ? offsets : void 0;
10134
- }
10135
- function toSkeletons(records) {
10136
- const roleById = new Map(records.map((r) => [r.id, text3(r.data.role)]));
10137
- return records.map((r) => {
10138
- const parentMessageId = text3(r.data.parentMessageId);
10139
- const model = text3(r.data.model);
10140
- const artifact = r.data.role === "user" && !!parentMessageId && roleById.get(parentMessageId) === "user";
10141
- return {
10142
- uniqueId: r.id,
10143
- messageId: r.sortKey,
10144
- conversationId: r.conversationId,
10145
- role: text3(r.data.role) ?? "",
10146
- createdAt: new Date(r.createdAt),
10147
- ...parentMessageId ? { parentMessageId } : {},
10148
- ...model ? { model } : {},
10149
- ...artifact ? { content: text3(r.data.content) ?? "" } : {}
10150
- };
10151
- });
10152
- }
10153
- function toSummary(record2) {
10154
- return {
10155
- uniqueId: record2.id,
10156
- conversationId: record2.id,
10157
- summary: text3(record2.data.summary) ?? "",
10158
- summarizedUpTo: text3(record2.data.summarizedUpTo) ?? "",
10159
- tokenCount: number(record2.data.tokenCount) ?? 0,
10160
- createdAt: new Date(record2.createdAt),
10161
- updatedAt: new Date(record2.updatedAt)
10162
- };
10163
- }
10164
- function links(data) {
10165
- return Array.isArray(data.links) ? data.links : [];
10166
- }
10167
- function applyLinkOps(data, ops) {
10168
- let current = links(data);
10169
- for (const op of ops) {
10170
- if ("clear" in op) {
10171
- current = [];
10172
- continue;
10173
- }
10174
- const byId = new Map(current.map((l) => [l.memoryId, l]));
10175
- const turn = /* @__PURE__ */ new Set();
10176
- op.add.forEach((item, i) => {
10177
- if (!item.memoryId || turn.has(item.memoryId)) return;
10178
- turn.add(item.memoryId);
10179
- const known = byId.get(item.memoryId);
10180
- if (known && known.createdAt >= op.at + i) return;
10181
- byId.set(item.memoryId, { memoryId: item.memoryId, score: item.score, createdAt: op.at + i });
10182
- });
10183
- current = [...byId.values()].sort((a, b) => a.createdAt - b.createdAt).slice(-MAX_PER_CONVERSATION);
10184
- }
10185
- return plain({ links: current });
10186
- }
10187
- function toLinks(record2) {
10188
- return links(record2.data).map((l) => ({
10189
- uniqueId: `${record2.id}:${l.memoryId}`,
10190
- conversationId: record2.id,
10191
- memoryId: l.memoryId,
10192
- score: l.score,
10193
- createdAt: new Date(l.createdAt)
10194
- }));
10195
- }
10196
- function bytes(value) {
10197
- return new TextEncoder().encode(JSON.stringify(value)).length;
10198
- }
10199
- function fitRecord(record2) {
10200
- if (record2.kind !== "message" || bytes(record2) <= recordBudgetBytes) return record2;
10201
- const data = { ...record2.data };
10202
- const truncated = [];
10203
- for (const field10 of droppable) {
10204
- if (bytes({ ...record2, data }) <= recordBudgetBytes) break;
10205
- if (data[field10] === void 0) continue;
10206
- delete data[field10];
10207
- truncated.push(field10);
10208
- }
10209
- const content = text3(data.content) ?? "";
10210
- const over = bytes({ ...record2, data }) - recordBudgetBytes;
10211
- if (over > 0) {
10212
- const encoded = new TextEncoder().encode(content);
10213
- data.content = new TextDecoder().decode(encoded.slice(0, Math.max(0, encoded.length - over - 64))).replace(/�$/, "");
10214
- truncated.push("content");
10215
- }
10216
- return { ...record2, data: { ...data, truncated } };
10217
- }
10218
- function acceptedVector(vector2) {
10219
- if (!vector2 || !acceptedDims.has(vector2.length)) return void 0;
10220
- if (!vector2.every(Number.isFinite) || !vector2.slice(0, storedDims).some((x) => x !== 0))
10221
- return void 0;
10222
- return [...vector2];
10223
- }
10224
- function normalized(vector2) {
10225
- const norm = Math.hypot(...vector2) || 1;
10226
- return vector2.map((x) => x / norm);
10227
- }
10228
- function fitChunks(chunks3) {
10229
- const usable = chunks3.map((c) => ({ ...c, vector: acceptedVector(c.vector) }));
10230
- if (usable.length === 0 || usable.some((c) => !c.vector)) return void 0;
10231
- if (usable.length <= maxChunks) return usable;
10232
- const out = [];
10233
- for (let g = 0; g < maxChunks; g++) {
10234
- const group = usable.slice(
10235
- Math.floor(g * usable.length / maxChunks),
10236
- Math.floor((g + 1) * usable.length / maxChunks)
10237
- );
10238
- const dims = group[0].vector.length;
10239
- const mean = new Array(dims).fill(0);
10240
- for (const c of group) {
10241
- const unit = normalized(c.vector);
10242
- for (let d = 0; d < dims; d++) mean[d] += unit[d];
10243
- }
10244
- out.push({
10245
- vector: normalized(mean),
10246
- startOffset: group[0].startOffset,
10247
- endOffset: group[group.length - 1].endOffset
10248
- });
10249
- }
10250
- return out;
10251
- }
10252
-
10253
- // src/lib/chatStore/anuma.ts
10254
- var ANUMA_CONVERSATION_ID = "anuma-1on1";
10255
- var maxAttempts = 3;
10256
- function anumaTurnContent(bubbles) {
10257
- return bubbles.join("\n\n");
10258
- }
10259
- function anumaLastReadSortKey(conversation) {
10260
- const record2 = conversation && "record" in conversation ? conversation.record : conversation;
10261
- const state = record2?.data.anuma;
10262
- if (!isObject(state)) return 0;
10263
- const sortKey = state.lastReadSortKey;
10264
- return Number.isSafeInteger(sortKey) && sortKey > 0 ? sortKey : 0;
10265
- }
10266
- function conflict(error) {
10267
- return error instanceof ChatStoreError && error.code === "version_conflict";
10268
- }
10269
- function live(snapshot) {
10270
- if (snapshot?.record.isDeleted)
10271
- throw new ChatStoreError("Anuma conversation was deleted", 409, "conversation_deleted");
10272
- return snapshot;
10273
- }
10274
- async function ensureAnumaConversation(store, options = {}) {
10275
- const existing = live(await store.get("conversation", ANUMA_CONVERSATION_ID, options.signal));
10276
- if (existing) return existing;
10277
- const at = (options.now ?? Date.now)();
10278
- const data = plain({ title: "Anuma", lastActivityAt: at, anuma: { lastReadSortKey: 0 } });
10279
- try {
10280
- const [created] = await store.putMany(
10281
- [
10282
- {
10283
- expectedVersion: 0,
10284
- record: {
10285
- kind: "conversation",
10286
- id: ANUMA_CONVERSATION_ID,
10287
- conversationId: "",
10288
- sortKey: conversationSortKey(data, at),
10289
- createdAt: at,
10290
- updatedAt: at,
10291
- isDeleted: false,
10292
- data
10293
- }
10294
- }
10295
- ],
10296
- options.signal
10297
- );
10298
- return created;
10299
- } catch (error) {
10300
- if (!conflict(error)) throw error;
10301
- const winner = live(await store.get("conversation", ANUMA_CONVERSATION_ID, options.signal));
10302
- if (!winner) throw error;
10303
- return winner;
10810
+ xhr.onabort = () => {
10811
+ options.signal?.removeEventListener("abort", abortHandler);
10812
+ push({ type: "error", error: makeAbortError() });
10813
+ finished = true;
10814
+ };
10815
+ xhr.send(options.body !== void 0 ? JSON.stringify(options.body) : void 0);
10304
10816
  }
10305
- }
10306
- async function markAnumaRead(store, sortKey, options = {}) {
10307
- if (!Number.isSafeInteger(sortKey) || sortKey < 0)
10308
- throw new ChatStoreError("Invalid Anuma read marker", 0, "invalid_record");
10309
- const now = options.now ?? Date.now;
10310
- for (let attempt = 1; ; attempt++) {
10311
- const current = await ensureAnumaConversation(store, options);
10312
- const read = anumaLastReadSortKey(current);
10313
- if (sortKey <= read) return current;
10314
- const record2 = current.record;
10315
- const state = isObject(record2.data.anuma) ? record2.data.anuma : {};
10316
- const data = plain({ ...record2.data, anuma: { ...state, lastReadSortKey: sortKey } });
10317
- try {
10318
- const [written] = await store.putMany(
10319
- [
10320
- {
10321
- expectedVersion: current.version,
10322
- record: {
10323
- ...record2,
10324
- data,
10325
- sortKey: conversationSortKey(data, record2.createdAt),
10326
- updatedAt: Math.max(record2.updatedAt, now())
10327
- }
10328
- }
10329
- ],
10330
- options.signal
10331
- );
10332
- return written;
10333
- } catch (error) {
10334
- if (!conflict(error) || attempt >= maxAttempts) throw error;
10817
+ async function* createStream() {
10818
+ if (options.signal?.aborted) throw makeAbortError();
10819
+ while (true) {
10820
+ await waitForItem();
10821
+ while (queue.length > 0) {
10822
+ const item = queue.shift();
10823
+ if (item.type === "done") return;
10824
+ if (item.type === "error") throw item.error;
10825
+ yield item.value;
10826
+ }
10335
10827
  }
10336
10828
  }
10337
- }
10338
- function countUnreadAnumaTurns(messages, lastReadSortKey) {
10339
- const answered = Math.max(
10340
- 0,
10341
- ...messages.filter((m) => m.role === "user").map((m) => m.messageId)
10342
- );
10343
- const floor = Math.max(lastReadSortKey, answered);
10344
- const turns = /* @__PURE__ */ new Set();
10345
- for (const m of messages) {
10346
- if (m.role !== "assistant" || m.messageId <= floor) continue;
10347
- turns.add(m.anuma?.turnId ? `turn:${m.anuma.turnId}` : `message:${m.uniqueId}`);
10829
+ return { stream: createStream() };
10830
+ };
10831
+
10832
+ // src/lib/chatStore/types.ts
10833
+ var ChatStoreError = class extends Error {
10834
+ constructor(message, status, code, retryAfterMs) {
10835
+ super(message);
10836
+ this.status = status;
10837
+ this.code = code;
10838
+ this.retryAfterMs = retryAfterMs;
10839
+ this.name = "ChatStoreError";
10348
10840
  }
10349
- return turns.size;
10350
- }
10841
+ };
10351
10842
 
10352
- // src/lib/db/chat/summaryOperations.ts
10353
- import { Q as Q4 } from "@nozbe/watermelondb";
10354
- function createSummaryContext(database, encryption = {}) {
10355
- return {
10356
- ...encryption,
10357
- database,
10358
- summariesCollection: database.get("conversation_summaries")
10359
- };
10360
- }
10361
- function modelToStored(record2, summary) {
10843
+ // src/lib/db/conversationMemory/operations.ts
10844
+ import { Q as Q3 } from "@nozbe/watermelondb";
10845
+ var MAX_PER_CONVERSATION = 200;
10846
+ function conversationMemoryToStored(row) {
10362
10847
  return {
10363
- uniqueId: record2.id,
10364
- conversationId: record2.conversationId,
10365
- summary,
10366
- summarizedUpTo: record2.summarizedUpTo,
10367
- tokenCount: record2.tokenCount,
10368
- createdAt: record2.createdAt,
10369
- updatedAt: record2.updatedAt
10848
+ uniqueId: row.id,
10849
+ conversationId: row.conversationId,
10850
+ memoryId: row.memoryId,
10851
+ score: row.score,
10852
+ createdAt: row.createdAt
10370
10853
  };
10371
10854
  }
10372
- async function getConversationSummaryOp(ctx, conversationId) {
10373
- const results = await ctx.summariesCollection.query(Q4.where("conversation_id", conversationId)).fetch();
10374
- if (results.length === 0) return null;
10375
- const record2 = results[0];
10376
- const stored2 = modelToStored(record2, record2.summary);
10377
- if (ctx.walletAddress && isEncrypted(stored2.summary)) {
10378
- if (ctx.signMessage) {
10379
- try {
10380
- await requestEncryptionKey(ctx.walletAddress, ctx.signMessage, ctx.embeddedWalletSigner);
10381
- } catch (error) {
10382
- getLogger().warn("Failed to request the encryption key for the summary:", error);
10383
- }
10384
- }
10385
- stored2.summary = await decryptField(stored2.summary, ctx.walletAddress);
10386
- } else if (ctx.walletAddress && ctx.signMessage && stored2.summary) {
10387
- try {
10388
- const encryptedSummary = await encryptField(
10389
- stored2.summary,
10390
- ctx.walletAddress,
10391
- ctx.signMessage,
10392
- ctx.embeddedWalletSigner
10393
- );
10394
- await ctx.database.write(async () => {
10395
- const current = await ctx.summariesCollection.query(Q4.where("id", record2.id)).fetch();
10396
- if (current[0]?.summary === stored2.summary) {
10397
- await current[0].update((rec) => {
10398
- rec.summary = encryptedSummary;
10399
- });
10400
- }
10401
- });
10402
- } catch (error) {
10403
- getLogger().warn("Failed to encrypt the legacy summary:", error);
10855
+ async function addConversationMemoriesOp(ctx, conversationId, items) {
10856
+ if (!conversationId || items.length === 0) return;
10857
+ const now = Date.now();
10858
+ await ctx.database.write(async () => {
10859
+ const existing = await ctx.conversationMemoryCollection.query(Q3.where("conversation_id", conversationId), Q3.sortBy("created_at", Q3.asc)).fetch();
10860
+ const seen = new Set(existing.map((r) => r.memoryId));
10861
+ const batchSeen = /* @__PURE__ */ new Set();
10862
+ const deduped = [];
10863
+ for (const it of items) {
10864
+ if (!it.memoryId || seen.has(it.memoryId) || batchSeen.has(it.memoryId)) continue;
10865
+ batchSeen.add(it.memoryId);
10866
+ deduped.push(it);
10404
10867
  }
10405
- }
10406
- return stored2;
10868
+ if (deduped.length === 0) return;
10869
+ const keptNew = deduped.length > MAX_PER_CONVERSATION ? deduped.slice(deduped.length - MAX_PER_CONVERSATION) : deduped;
10870
+ const keepExistingCount = MAX_PER_CONVERSATION - keptNew.length;
10871
+ const pruneCount = Math.max(0, existing.length - keepExistingCount);
10872
+ const toPrune = existing.slice(0, pruneCount);
10873
+ const ops = [
10874
+ ...toPrune.map((r) => r.prepareDestroyPermanently()),
10875
+ ...keptNew.map(
10876
+ (it, i) => ctx.conversationMemoryCollection.prepareCreate((r) => {
10877
+ r._setRaw("conversation_id", conversationId);
10878
+ r._setRaw("memory_id", it.memoryId);
10879
+ r._setRaw("score", it.score);
10880
+ r._setRaw("created_at", now + i);
10881
+ })
10882
+ )
10883
+ ];
10884
+ await ctx.database.batch(...ops);
10885
+ });
10407
10886
  }
10408
- async function upsertConversationSummaryOp(ctx, conversationId, summary, summarizedUpTo, tokenCount) {
10409
- const revision2 = getConversationRevision(ctx.database, conversationId);
10410
- let record2;
10411
- const encryptedSummary = ctx.walletAddress ? await encryptField(summary, ctx.walletAddress, ctx.signMessage, ctx.embeddedWalletSigner) : summary;
10887
+ async function recordConversationMemoriesOp(ctx, conversationId, items, now = Date.now()) {
10888
+ const turn = /* @__PURE__ */ new Map();
10889
+ items.forEach((item, i) => {
10890
+ if (item.memoryId && !turn.has(item.memoryId))
10891
+ turn.set(item.memoryId, { score: item.score, createdAt: now + i });
10892
+ });
10893
+ if (!conversationId || turn.size === 0) return;
10894
+ const kept = new Map([...turn].slice(-MAX_PER_CONVERSATION));
10412
10895
  await ctx.database.write(async () => {
10413
- assertConversationRevision(ctx.database, conversationId, revision2);
10414
- const existing = await ctx.summariesCollection.query(Q4.where("conversation_id", conversationId)).fetch();
10415
- if (existing.length > 0) {
10416
- record2 = await existing[0].update((rec) => {
10417
- rec.summary = encryptedSummary;
10418
- rec.summarizedUpTo = summarizedUpTo;
10419
- rec.tokenCount = tokenCount;
10420
- });
10421
- } else {
10422
- record2 = await ctx.summariesCollection.create((rec) => {
10423
- rec.conversationId = conversationId;
10424
- rec.summary = encryptedSummary;
10425
- rec.summarizedUpTo = summarizedUpTo;
10426
- rec.tokenCount = tokenCount;
10427
- });
10428
- }
10896
+ const existing = await ctx.conversationMemoryCollection.query(Q3.where("conversation_id", conversationId), Q3.sortBy("created_at", Q3.asc)).fetch();
10897
+ const earlier = existing.filter((r) => !turn.has(r.memoryId));
10898
+ const linked = new Set(existing.map((r) => r.memoryId));
10899
+ await ctx.database.batch(
10900
+ ...earlier.slice(0, Math.max(0, earlier.length - (MAX_PER_CONVERSATION - kept.size))).map((r) => r.prepareDestroyPermanently()),
10901
+ ...existing.filter((r) => turn.has(r.memoryId) && !kept.has(r.memoryId)).map((r) => r.prepareDestroyPermanently()),
10902
+ ...existing.filter((r) => kept.has(r.memoryId)).map(
10903
+ (r) => r.prepareUpdate((row) => {
10904
+ row._setRaw("score", kept.get(r.memoryId).score);
10905
+ row._setRaw("created_at", kept.get(r.memoryId).createdAt);
10906
+ })
10907
+ ),
10908
+ ...[...kept].filter(([memoryId]) => !linked.has(memoryId)).map(
10909
+ ([memoryId, link]) => ctx.conversationMemoryCollection.prepareCreate((row) => {
10910
+ row._setRaw("conversation_id", conversationId);
10911
+ row._setRaw("memory_id", memoryId);
10912
+ row._setRaw("score", link.score);
10913
+ row._setRaw("created_at", link.createdAt);
10914
+ })
10915
+ )
10916
+ );
10429
10917
  });
10430
- return modelToStored(record2, summary);
10431
10918
  }
10432
- async function deleteConversationSummaryOp(ctx, conversationId) {
10919
+ async function getConversationMemoriesOp(ctx, conversationId) {
10920
+ if (!conversationId) return [];
10921
+ const rows = await ctx.conversationMemoryCollection.query(Q3.where("conversation_id", conversationId), Q3.sortBy("created_at", Q3.asc)).fetch();
10922
+ return rows.map(conversationMemoryToStored);
10923
+ }
10924
+ async function clearConversationMemoriesOp(ctx, conversationId) {
10925
+ if (!conversationId) return;
10433
10926
  await ctx.database.write(async () => {
10434
- const existing = await ctx.summariesCollection.query(Q4.where("conversation_id", conversationId)).fetch();
10435
- for (const record2 of existing) {
10436
- await record2.destroyPermanently();
10437
- }
10927
+ const rows = await ctx.conversationMemoryCollection.query(Q3.where("conversation_id", conversationId)).fetch();
10928
+ if (rows.length === 0) return;
10929
+ await ctx.database.batch(...rows.map((r) => r.prepareDestroyPermanently()));
10438
10930
  });
10439
10931
  }
10440
10932
 
10441
- // src/lib/db/conversationMemory/models.ts
10442
- import { Model as Model3 } from "@nozbe/watermelondb";
10443
- import { date as date2, field as field2, readonly, text as text4 } from "@nozbe/watermelondb/decorators";
10444
- var ConversationMemory = class extends Model3 {
10445
- };
10446
- ConversationMemory.table = "conversation_memory";
10447
- ConversationMemory.associations = {};
10448
- __decorateClass([
10449
- text4("conversation_id")
10450
- ], ConversationMemory.prototype, "conversationId", 2);
10451
- __decorateClass([
10452
- text4("memory_id")
10453
- ], ConversationMemory.prototype, "memoryId", 2);
10454
- __decorateClass([
10455
- field2("score")
10456
- ], ConversationMemory.prototype, "score", 2);
10457
- __decorateClass([
10458
- readonly,
10459
- date2("created_at")
10460
- ], ConversationMemory.prototype, "createdAt", 2);
10461
-
10462
- // src/lib/chatStore/chatImports.ts
10463
- var importBatch = 100;
10464
- var importChunks = 200;
10465
- var importPlaintextBytes = 39e5;
10466
- function ms(date10, fallback = 1) {
10467
- const value = date10?.getTime();
10468
- return value && Number.isFinite(value) ? Math.max(1, Math.floor(value)) : fallback;
10933
+ // src/lib/chatStore/vaultRecords.ts
10934
+ var PINNED_SORT_BASE = 2 ** 51;
10935
+ var recordBudgetBytes = 104e4;
10936
+ var storedDims = 1536;
10937
+ var acceptedDims = /* @__PURE__ */ new Set([1536, 4096]);
10938
+ var maxChunks = 16;
10939
+ var droppable = ["thoughtProcess", "thinking", "sources", "toolCallEvents"];
10940
+ var messageMetadata = /* @__PURE__ */ new Set(["conversationId", "uniqueId", "vector", "chunks"]);
10941
+ var alwaysStored = /* @__PURE__ */ new Set(["role", "content", "responseDuration"]);
10942
+ var maxBubbles = 3;
10943
+ var maxTurnIdBytes = 128;
10944
+ var maxBubbleBytes = 16384;
10945
+ var anumaOrigins = /* @__PURE__ */ new Set(["intro", "reply"]);
10946
+ function plain(value) {
10947
+ return JSON.parse(JSON.stringify(value ?? {}));
10469
10948
  }
10470
- function bytes2(value) {
10471
- return new TextEncoder().encode(JSON.stringify(value)).length;
10949
+ function text3(value) {
10950
+ return typeof value === "string" ? value : void 0;
10472
10951
  }
10473
- function conversationRecord(conversation, messages) {
10474
- const createdAt = ms(conversation.createdAt);
10475
- const data = plain({
10476
- ...contextFields(conversation.context),
10477
- title: conversation.title,
10478
- projectId: conversation.projectId || void 0,
10479
- pinnedAt: conversation.pinnedAt ? ms(conversation.pinnedAt) : void 0,
10480
- lastActivityAt: Math.max(
10481
- ms(conversation.updatedAt, createdAt),
10482
- ...messages.map((m) => ms(m.createdAt))
10483
- )
10484
- });
10485
- return {
10486
- record: {
10487
- kind: "conversation",
10488
- id: conversation.conversationId,
10489
- conversationId: "",
10490
- sortKey: conversationSortKey(data, createdAt),
10491
- createdAt,
10492
- updatedAt: ms(conversation.updatedAt, createdAt),
10493
- isDeleted: false,
10494
- data
10495
- }
10496
- };
10952
+ function number(value) {
10953
+ return typeof value === "number" ? value : void 0;
10497
10954
  }
10498
- function messageRecord(message) {
10499
- const {
10500
- uniqueId: _uniqueId,
10501
- messageId: _messageId,
10502
- createdAt: _createdAt,
10503
- updatedAt: _updatedAt,
10504
- decryptionStatus: _status,
10505
- chunks: chunks3,
10506
- vector: vector2,
10507
- ...fields
10508
- } = message;
10509
- const fitted = chunks3?.length ? fitChunks(chunks3) : void 0;
10510
- const single = fitted ? void 0 : acceptedVector(vector2);
10511
- const embeddings2 = fitted?.map((c) => c.vector) ?? (single ? [single] : void 0);
10512
- const record2 = fitRecord({
10513
- kind: "message",
10514
- id: message.uniqueId,
10515
- conversationId: message.conversationId,
10516
- sortKey: message.messageId,
10517
- createdAt: ms(message.createdAt),
10518
- updatedAt: ms(message.updatedAt, ms(message.createdAt)),
10519
- isDeleted: false,
10520
- data: {
10521
- ...messageData(fields),
10522
- ...fitted ? { chunkOffsets: fitted.map((c) => [c.startOffset, c.endOffset]) } : {}
10523
- }
10524
- });
10955
+ function readAnumaTurn(value) {
10956
+ if (!isObject(value)) return void 0;
10957
+ const { turnId, bubbles, origin } = value;
10958
+ if (typeof turnId !== "string" || turnId.length === 0 || new TextEncoder().encode(turnId).length > maxTurnIdBytes)
10959
+ return void 0;
10960
+ if (bubbles !== void 0 && (!Array.isArray(bubbles) || bubbles.length < 1 || bubbles.length > maxBubbles || !bubbles.every((b) => typeof b === "string") || bubbles.reduce((sum, b) => sum + new TextEncoder().encode(b).length, 0) > maxBubbleBytes))
10961
+ return void 0;
10962
+ if (origin !== void 0 && (typeof origin !== "string" || !anumaOrigins.has(origin)))
10963
+ return void 0;
10525
10964
  return {
10526
- record: record2,
10527
- ...embeddings2 ? { embeddings: embeddings2 } : {},
10528
- ...embeddings2 && message.embeddingModel ? { embeddingModel: message.embeddingModel } : {}
10965
+ turnId,
10966
+ ...bubbles !== void 0 ? { bubbles: [...bubbles] } : {},
10967
+ ...origin !== void 0 ? { origin } : {}
10529
10968
  };
10530
10969
  }
10531
- function batches(records) {
10532
- const out = [];
10533
- let batch = [];
10534
- let chunks3 = 0;
10535
- let size = 0;
10536
- for (const item of records) {
10537
- const vectors = item.embeddings?.length ?? 0;
10538
- const itemSize = bytes2(item.record);
10539
- if (batch.length > 0 && (batch.length >= importBatch || chunks3 + vectors > importChunks || size + itemSize > importPlaintextBytes)) {
10540
- out.push(batch);
10541
- batch = [];
10542
- chunks3 = 0;
10543
- size = 0;
10544
- }
10545
- batch.push(item);
10546
- chunks3 += vectors;
10547
- size += itemSize;
10548
- }
10549
- if (batch.length) out.push(batch);
10550
- return out;
10970
+ function isObject(value) {
10971
+ return value !== null && typeof value === "object" && !Array.isArray(value);
10551
10972
  }
10552
-
10553
- // src/lib/db/entities/types.ts
10554
- var ENTITY_KINDS = [
10555
- "person",
10556
- "organization",
10557
- "place",
10558
- "event",
10559
- "product",
10560
- "thing",
10561
- "concept",
10562
- "other"
10563
- ];
10564
- function normalizeEntityName(name) {
10565
- return name.trim().toLowerCase();
10973
+ function conversationSortKey(data, createdAt) {
10974
+ const pinnedAt = number(data.pinnedAt);
10975
+ if (pinnedAt) return PINNED_SORT_BASE + pinnedAt;
10976
+ return number(data.lastActivityAt) ?? createdAt;
10566
10977
  }
10567
- var TOPIC_SALIENCE_WEIGHTS = { primary: 1, secondary: 0.6, mention: 0.3 };
10568
- function topicWeight(topic2) {
10569
- return topic2.source === "user" ? 1 : topic2.weight ?? 1;
10978
+ var CONTEXT_FIELD_PREFIX = "context.";
10979
+ function contextField(key3) {
10980
+ return `${CONTEXT_FIELD_PREFIX}${key3}`;
10570
10981
  }
10571
- function primaryTopic(topics) {
10572
- let best = null;
10573
- for (const topic2 of topics ?? []) {
10574
- if (best === null || topicWeight(topic2) > topicWeight(best)) best = topic2;
10575
- }
10576
- return best;
10982
+ function contextFields(context) {
10983
+ return plain(
10984
+ Object.fromEntries(Object.entries(context ?? {}).map(([k, value]) => [contextField(k), value]))
10985
+ );
10577
10986
  }
10578
- function parseTopicWeight(value) {
10579
- return typeof value === "number" && value > 0 && value <= 1 ? value : void 0;
10987
+ function conversationContext(data) {
10988
+ const entries = Object.entries(data).filter(([field10]) => field10.startsWith(CONTEXT_FIELD_PREFIX)).map(([field10, value]) => [field10.slice(CONTEXT_FIELD_PREFIX.length), value]);
10989
+ return entries.length > 0 ? plain(Object.fromEntries(entries)) : void 0;
10580
10990
  }
10581
- function serializeTopics(topics) {
10582
- return JSON.stringify(topics);
10991
+ function toConversation(record2) {
10992
+ const pinnedAt = number(record2.data.pinnedAt);
10993
+ const projectId = text3(record2.data.projectId);
10994
+ const context = conversationContext(record2.data);
10995
+ return {
10996
+ uniqueId: record2.id,
10997
+ conversationId: record2.id,
10998
+ title: text3(record2.data.title) ?? "",
10999
+ ...projectId ? { projectId } : {},
11000
+ createdAt: new Date(record2.createdAt),
11001
+ updatedAt: new Date(record2.updatedAt),
11002
+ isDeleted: record2.isDeleted,
11003
+ pinnedAt: pinnedAt ? new Date(pinnedAt) : null,
11004
+ ...context ? { context } : {}
11005
+ };
10583
11006
  }
10584
- function parseTopics(value) {
10585
- if (typeof value !== "string" || value.length === 0) return null;
10586
- let parsed;
10587
- try {
10588
- parsed = JSON.parse(value);
10589
- } catch {
10590
- return null;
10591
- }
10592
- if (!Array.isArray(parsed)) return null;
10593
- const out = [];
10594
- for (const raw of parsed) {
10595
- if (typeof raw !== "object" || raw === null) continue;
10596
- const { name, kind, source, weight } = raw;
10597
- if (typeof name !== "string" || name.trim().length === 0) continue;
10598
- const parsedWeight = parseTopicWeight(weight);
10599
- out.push({
10600
- name,
10601
- ...typeof kind === "string" && kind.length > 0 ? { kind } : {},
10602
- source: source === "user" ? "user" : "auto",
10603
- ...parsedWeight !== void 0 ? { weight: parsedWeight } : {}
10604
- });
10605
- }
10606
- return out;
11007
+ function anumaTurnData(value) {
11008
+ if (value === void 0) return void 0;
11009
+ const turn = readAnumaTurn(value);
11010
+ if (!turn) throw new ChatStoreError("Invalid Anuma turn", 0, "invalid_record");
11011
+ return turn;
10607
11012
  }
10608
-
10609
- // src/lib/memory/store/remoteTopics.ts
10610
- function topic(value) {
10611
- const t = value;
10612
- if (!t || typeof t.name !== "string" || !t.name || !Number.isSafeInteger(t.version) || t.version < 1 || typeof t.is_deleted !== "boolean" || typeof t.server_updated_at !== "string" || t.kind !== void 0 && typeof t.kind !== "string")
10613
- throw new Error("Invalid nearby topic");
10614
- return t;
11013
+ function messageData(options) {
11014
+ const anuma = anumaTurnData(options.anuma);
11015
+ return plain({
11016
+ ...Object.fromEntries(
11017
+ Object.entries(options).filter(
11018
+ ([key3, value]) => key3 !== "anuma" && !messageMetadata.has(key3) && (!!value || alwaysStored.has(key3))
11019
+ )
11020
+ ),
11021
+ ...anuma ? { anuma } : {}
11022
+ });
10615
11023
  }
10616
- function createRemoteTopicPersistence(request) {
11024
+ function toMessage(record2) {
11025
+ const { chunkOffsets: _offsets, truncated: _truncated, anuma: rawAnuma, ...fields } = record2.data;
11026
+ const anuma = readAnumaTurn(rawAnuma);
10617
11027
  return {
10618
- topics: async () => {
10619
- const out = [];
10620
- let cursor;
10621
- do {
10622
- const page = await request(
10623
- "/topics?limit=200" + (cursor ? `&cursor=${encodeURIComponent(cursor)}` : ""),
10624
- {}
10625
- );
10626
- if (!Array.isArray(page.items) || page.next_cursor !== void 0 && typeof page.next_cursor !== "string")
10627
- throw new Error("Invalid nearby topic page");
10628
- out.push(...page.items.map(topic));
10629
- cursor = page.next_cursor;
10630
- } while (cursor);
10631
- return out;
10632
- },
10633
- putTopic: async (value, expectedVersion) => {
10634
- if (!Number.isSafeInteger(expectedVersion) || expectedVersion < 0)
10635
- throw new Error("Expected topic version must be a nonnegative integer");
10636
- return topic(
10637
- await request("/topics", {
10638
- method: "PUT",
10639
- body: JSON.stringify({ topic: value, expected_version: expectedVersion })
10640
- })
10641
- );
10642
- },
10643
- importTopics: async (topics, signal) => {
10644
- for (let i = 0; i < topics.length; i += 50) {
10645
- signal?.throwIfAborted();
10646
- await request(
10647
- "/topics/import",
10648
- {
10649
- method: "POST",
10650
- body: JSON.stringify({ topics: topics.slice(i, i + 50) })
10651
- },
10652
- signal
10653
- );
10654
- }
10655
- signal?.throwIfAborted();
10656
- }
11028
+ ...anuma ? { anuma } : {},
11029
+ ...fields,
11030
+ uniqueId: record2.id,
11031
+ messageId: record2.sortKey,
11032
+ conversationId: record2.conversationId,
11033
+ createdAt: new Date(record2.createdAt),
11034
+ updatedAt: new Date(record2.updatedAt)
10657
11035
  };
10658
11036
  }
10659
- async function createRemoteTopic(persistence, name) {
10660
- const canonical = normalizeEntityName(name);
10661
- if (!canonical) return null;
10662
- const existing = (await persistence.topics()).find((t) => t.name === canonical);
10663
- if (existing && !existing.is_deleted) return canonical;
10664
- try {
10665
- await persistence.putTopic(
10666
- { name: canonical, is_deleted: false, ...existing?.kind && { kind: existing.kind } },
10667
- existing?.version ?? 0
10668
- );
10669
- } catch (error) {
10670
- if (!(error instanceof RemoteMemoryError) || error.code !== "version_conflict") throw error;
10671
- const current = (await persistence.topics()).find((t) => t.name === canonical);
10672
- if (!current || current.is_deleted) throw error;
10673
- }
10674
- return canonical;
11037
+ function chunkOffsets(record2) {
11038
+ const offsets = record2.data.chunkOffsets;
11039
+ return Array.isArray(offsets) ? offsets : void 0;
10675
11040
  }
10676
-
10677
- // src/lib/memory/store/remoteWork.ts
10678
- function createRemoteWorkPersistence(request) {
11041
+ function toSkeletons(records) {
11042
+ const roleById = new Map(records.map((r) => [r.id, text3(r.data.role)]));
11043
+ return records.map((r) => {
11044
+ const parentMessageId = text3(r.data.parentMessageId);
11045
+ const model = text3(r.data.model);
11046
+ const artifact = r.data.role === "user" && !!parentMessageId && roleById.get(parentMessageId) === "user";
11047
+ return {
11048
+ uniqueId: r.id,
11049
+ messageId: r.sortKey,
11050
+ conversationId: r.conversationId,
11051
+ role: text3(r.data.role) ?? "",
11052
+ createdAt: new Date(r.createdAt),
11053
+ ...parentMessageId ? { parentMessageId } : {},
11054
+ ...model ? { model } : {},
11055
+ ...artifact ? { content: text3(r.data.content) ?? "" } : {}
11056
+ };
11057
+ });
11058
+ }
11059
+ function toSummary(record2) {
10679
11060
  return {
10680
- claimWork: async (kind, processorVersion, token) => {
10681
- const result = await request("/work/claim", {
10682
- method: "POST",
10683
- body: JSON.stringify({
10684
- kind,
10685
- processor_version: processorVersion,
10686
- ...token && { token }
10687
- })
10688
- });
10689
- if (!result || typeof result !== "object") throw new Error("Invalid nearby work lease");
10690
- const lease = result.lease;
10691
- if (lease === void 0) return null;
10692
- if (!lease || lease.kind !== kind || typeof lease.token !== "string" || !lease.token || lease.processor_version !== processorVersion || typeof lease.expires_at !== "string" || !Number.isFinite(Date.parse(lease.expires_at)))
10693
- throw new Error("Invalid nearby work lease");
10694
- return lease;
10695
- },
10696
- releaseWork: async (kind, token) => {
10697
- await request("/work/release", { method: "POST", body: JSON.stringify({ kind, token }) });
10698
- }
11061
+ uniqueId: record2.id,
11062
+ conversationId: record2.id,
11063
+ summary: text3(record2.data.summary) ?? "",
11064
+ summarizedUpTo: text3(record2.data.summarizedUpTo) ?? "",
11065
+ tokenCount: number(record2.data.tokenCount) ?? 0,
11066
+ createdAt: new Date(record2.createdAt),
11067
+ updatedAt: new Date(record2.updatedAt)
10699
11068
  };
10700
11069
  }
10701
- async function withRemoteWorkLease(persistence, kind, processorVersion, task) {
10702
- const lease = await persistence.claimWork(kind, processorVersion);
10703
- if (!lease) return null;
10704
- const controller = new AbortController();
10705
- const leaseLostError = new RemoteMemoryError("Maintenance lease lost", 409, "work_lease_expired");
10706
- const cancelled = new Promise((_resolve, reject) => {
10707
- controller.signal.addEventListener("abort", () => reject(leaseLostError), {
10708
- once: true
10709
- });
10710
- });
10711
- let renewing;
10712
- const timer = setInterval(() => {
10713
- if (renewing || controller.signal.aborted) return;
10714
- renewing = persistence.claimWork(kind, processorVersion, lease.token).then((renewed) => {
10715
- if (!renewed) controller.abort(leaseLostError);
10716
- }).catch((error) => getLogger().warn("[memory/work] Lease renewal failed:", error)).finally(() => {
10717
- renewing = void 0;
11070
+ function links(data) {
11071
+ return Array.isArray(data.links) ? data.links : [];
11072
+ }
11073
+ function applyLinkOps(data, ops) {
11074
+ let current = links(data);
11075
+ for (const op of ops) {
11076
+ if ("clear" in op) {
11077
+ current = [];
11078
+ continue;
11079
+ }
11080
+ const byId = new Map(current.map((l) => [l.memoryId, l]));
11081
+ const turn = /* @__PURE__ */ new Set();
11082
+ op.add.forEach((item, i) => {
11083
+ if (!item.memoryId || turn.has(item.memoryId)) return;
11084
+ turn.add(item.memoryId);
11085
+ const known = byId.get(item.memoryId);
11086
+ if (known && known.createdAt >= op.at + i) return;
11087
+ byId.set(item.memoryId, { memoryId: item.memoryId, score: item.score, createdAt: op.at + i });
10718
11088
  });
10719
- }, 3e4);
10720
- try {
10721
- const result = await Promise.race([task(lease.token, controller.signal), cancelled]);
10722
- await renewing;
10723
- return controller.signal.aborted ? null : result;
10724
- } catch (error) {
10725
- if (controller.signal.aborted || isRemoteWorkLeaseExpired(error)) return null;
10726
- throw error;
10727
- } finally {
10728
- clearInterval(timer);
10729
- await renewing;
10730
- await persistence.releaseWork(kind, lease.token).catch((error) => getLogger().warn("[memory/work] Lease release failed:", error));
11089
+ current = [...byId.values()].sort((a, b) => a.createdAt - b.createdAt).slice(-MAX_PER_CONVERSATION);
10731
11090
  }
11091
+ return plain({ links: current });
10732
11092
  }
10733
- function isRemoteWorkLeaseExpired(error) {
10734
- return error instanceof RemoteMemoryError && error.code === "work_lease_expired";
11093
+ function toLinks(record2) {
11094
+ return links(record2.data).map((l) => ({
11095
+ uniqueId: `${record2.id}:${l.memoryId}`,
11096
+ conversationId: record2.id,
11097
+ memoryId: l.memoryId,
11098
+ score: l.score,
11099
+ createdAt: new Date(l.createdAt)
11100
+ }));
10735
11101
  }
10736
-
10737
- // src/lib/memory/store/remotePersistence.ts
10738
- var RemoteMemoryError = class extends Error {
10739
- constructor(message, status, code, retryAfterMs, rateLimitBucket) {
10740
- super(message);
10741
- this.status = status;
10742
- this.code = code;
10743
- this.retryAfterMs = retryAfterMs;
10744
- this.rateLimitBucket = rateLimitBucket;
10745
- this.name = "RemoteMemoryError";
10746
- }
10747
- };
10748
- var ciphertextPattern = /^enc:v\d+:[0-9a-f]+$/i;
10749
- var memoryFields = {
10750
- memory_id: true,
10751
- content: true,
10752
- scope: true,
10753
- created_at: true,
10754
- updated_at: true,
10755
- is_deleted: true,
10756
- embedding: true,
10757
- embedding_model: true,
10758
- kind: true,
10759
- kind_value: true,
10760
- level: true,
10761
- folder_id: true,
10762
- source_chunk_ids: true,
10763
- proof_count: true,
10764
- source: true,
10765
- event_time_start: true,
10766
- event_time_end: true,
10767
- event_time_kind: true,
10768
- topics_user_managed: true,
10769
- topics: true,
10770
- topics_updated_at: true,
10771
- media: true,
10772
- topics_extracted_at: true,
10773
- topics_extracted_version: true,
10774
- superseded_by: true,
10775
- superseded_at: true,
10776
- last_observed_at: true,
10777
- fact_type: true,
10778
- archived_at: true,
10779
- trust_tier: true,
10780
- visibility: true,
10781
- twin_opt_in: true,
10782
- published_at: true,
10783
- geohash: true
10784
- };
10785
- var candidateListLimits = [
10786
- ["scopes", 20],
10787
- ["fact_types", 20],
10788
- ["memory_ids", 1e3],
10789
- ["force_ids", 100]
10790
- ];
10791
- var queryListLimits = [
10792
- ["scopes", 20],
10793
- ["fact_types", 20],
10794
- ["visibility", 20],
10795
- ["memory_ids", 1e3],
10796
- ["topics", 100]
10797
- ];
10798
- var queryFields = [
10799
- "include_deleted",
10800
- "include_archived",
10801
- "include_quarantined",
10802
- "include_superseded",
10803
- "archived_only",
10804
- "scopes",
10805
- "fact_types",
10806
- "visibility",
10807
- "memory_ids",
10808
- "topics",
10809
- "updated_after",
10810
- "topics_due",
10811
- "topics_extracted",
10812
- "event_time_overlaps",
10813
- "order",
10814
- "cursor",
10815
- "limit",
10816
- "include_embeddings"
10817
- ];
10818
- function record(value) {
10819
- return typeof value === "object" && value !== null && !Array.isArray(value);
11102
+ function bytes(value) {
11103
+ return new TextEncoder().encode(JSON.stringify(value)).length;
10820
11104
  }
10821
- function memoryRecord(value) {
10822
- if (!record(value) || !record(value.memory) || typeof value.memory.memory_id !== "string" || typeof value.memory.content !== "string" || typeof value.memory.scope !== "string" || typeof value.memory.created_at !== "number" || typeof value.memory.updated_at !== "number" || typeof value.memory.is_deleted !== "boolean" || !Number.isSafeInteger(value.version) || value.version < 1 || typeof value.server_updated_at !== "string" || value.score !== void 0 && (typeof value.score !== "number" || !Number.isFinite(value.score))) {
10823
- throw new Error("Invalid nearby private-memory response");
11105
+ function fitRecord(record2) {
11106
+ if (record2.kind !== "message" || bytes(record2) <= recordBudgetBytes) return record2;
11107
+ const data = { ...record2.data };
11108
+ const truncated = [];
11109
+ for (const field10 of droppable) {
11110
+ if (bytes({ ...record2, data }) <= recordBudgetBytes) break;
11111
+ if (data[field10] === void 0) continue;
11112
+ delete data[field10];
11113
+ truncated.push(field10);
10824
11114
  }
10825
- return value;
10826
- }
10827
- function createNearbyRequest(options, apiPath = "/api/private-memories") {
10828
- const base = new URL(options.baseUrl);
10829
- if (!/^https?:$/.test(base.protocol) || base.username || base.password || base.search || base.hash) {
10830
- throw new Error(
10831
- "baseUrl must be an HTTP(S) origin or base path without credentials, query or fragment"
10832
- );
11115
+ const content = text3(data.content) ?? "";
11116
+ const over = bytes({ ...record2, data }) - recordBudgetBytes;
11117
+ if (over > 0) {
11118
+ const encoded = new TextEncoder().encode(content);
11119
+ data.content = new TextDecoder().decode(encoded.slice(0, Math.max(0, encoded.length - over - 64))).replace(/�$/, "");
11120
+ truncated.push("content");
10833
11121
  }
10834
- const timeoutMs = options.timeoutMs ?? 3e4;
10835
- if (!Number.isFinite(timeoutMs) || timeoutMs <= 0)
10836
- throw new Error("timeoutMs must be a positive number");
10837
- const root = base.href.replace(/\/$/, "") + apiPath;
10838
- const fetcher = options.fetch ?? globalThis.fetch;
10839
- const cooldowns = /* @__PURE__ */ new Map();
10840
- const memoryBudget = (path, method) => {
10841
- if (method === "PUT")
10842
- return path === "/topics" ? "private-memory-import" : "private-memory-write";
10843
- if (method === "POST" && ["/import", "/memories/batch", "/topics/import"].includes(path))
10844
- return "private-memory-import";
10845
- return "private-memory-read";
10846
- };
10847
- const request = async (path, init = {}, signal) => {
10848
- const controller = new AbortController();
10849
- let timedOut = false;
10850
- const timer = setTimeout(() => {
10851
- timedOut = true;
10852
- controller.abort();
10853
- }, timeoutMs);
10854
- const forwardAbort = () => controller.abort(signal?.reason);
10855
- if (signal?.aborted) forwardAbort();
10856
- else signal?.addEventListener("abort", forwardAbort, { once: true });
10857
- let rejectAborted;
10858
- const aborted2 = new Promise((_, reject) => {
10859
- rejectAborted = () => reject(
10860
- controller.signal.reason instanceof Error ? controller.signal.reason : new Error("Nearby request aborted")
10861
- );
10862
- if (controller.signal.aborted) rejectAborted();
10863
- else controller.signal.addEventListener("abort", rejectAborted, { once: true });
10864
- });
10865
- aborted2.catch(() => void 0);
10866
- try {
10867
- const token = await Promise.race([options.getToken(), aborted2]);
10868
- if (controller.signal.aborted) await aborted2;
10869
- if (!token) throw new RemoteMemoryError("Authentication required", 401);
10870
- const pathname = path.split("?")[0];
10871
- const route = `${init.method ?? "GET"} ${pathname}`;
10872
- const budgets = ["*", route];
10873
- if (apiPath === "/api/private-memories")
10874
- budgets.push("private-memory-ip", memoryBudget(pathname, init.method ?? "GET"));
10875
- for (const bucket of budgets) {
10876
- const cooldown = cooldowns.get(bucket);
10877
- if (!cooldown) continue;
10878
- const remaining = cooldown.until - Date.now();
10879
- if (remaining > 0)
10880
- throw new RemoteMemoryError(
10881
- cooldown.error.message,
10882
- 429,
10883
- cooldown.error.code,
10884
- remaining,
10885
- cooldown.error.rateLimitBucket
10886
- );
10887
- cooldowns.delete(bucket);
10888
- }
10889
- const response = await fetcher(root + path, {
10890
- ...init,
10891
- headers: {
10892
- Authorization: `Bearer ${token}`,
10893
- ...init.body ? { "Content-Type": "application/json" } : {}
10894
- },
10895
- redirect: "error",
10896
- signal: controller.signal
10897
- });
10898
- if (!response.ok) {
10899
- const body = await response.json().catch(() => null);
10900
- const retryAfter = parseRetryAfterMs(response.headers.get("Retry-After"));
10901
- const error = new RemoteMemoryError(
10902
- record(body) && typeof body.detail === "string" ? body.detail : `Nearby request failed (${response.status})`,
10903
- response.status,
10904
- record(body) && typeof body.code === "string" ? body.code : void 0,
10905
- retryAfter ?? void 0,
10906
- record(body) && typeof body.rate_limit_bucket === "string" ? body.rate_limit_bucket : void 0
10907
- );
10908
- if (response.status === 429 && retryAfter !== null && retryAfter > 0 && (!error.code || error.code === "rate_limited")) {
10909
- const bucket = error.rateLimitBucket && budgets.includes(error.rateLimitBucket) ? error.rateLimitBucket : record(body) && (body.limit_scope === "account" || body.limit_scope === "ip") && !error.rateLimitBucket ? "*" : route;
10910
- cooldowns.set(bucket, { until: Date.now() + retryAfter, error });
10911
- }
10912
- throw error;
10913
- }
10914
- if (response.status === 204) return null;
10915
- return await response.json();
10916
- } catch (error) {
10917
- if (timedOut) throw new RemoteMemoryError("Nearby request timed out", 408, "timeout");
10918
- throw error;
10919
- } finally {
10920
- clearTimeout(timer);
10921
- signal?.removeEventListener("abort", forwardAbort);
10922
- if (rejectAborted) controller.signal.removeEventListener("abort", rejectAborted);
10923
- }
10924
- };
10925
- return request;
11122
+ return { ...record2, data: { ...data, truncated } };
10926
11123
  }
10927
- async function createRemoteMemoryPersistence(options) {
10928
- if (!options.keyId || options.keyId.length > 256)
10929
- throw new Error("A canonical keyId is required");
10930
- const request = createNearbyRequest(options);
10931
- const account = await request("/account", {}, options.signal);
10932
- if (!record(account) || account.state !== "active") {
10933
- throw new RemoteMemoryError(
10934
- "Complete and verify migration before using remote memories",
10935
- 409,
10936
- "memory_not_active"
10937
- );
10938
- }
10939
- if (account.key_id !== options.keyId) {
10940
- throw new RemoteMemoryError(
10941
- "The canonical memory key does not match this account",
10942
- 409,
10943
- "key_mismatch"
11124
+ function acceptedVector(vector2) {
11125
+ if (!vector2 || !acceptedDims.has(vector2.length)) return void 0;
11126
+ if (!vector2.every(Number.isFinite) || !vector2.slice(0, storedDims).some((x) => x !== 0))
11127
+ return void 0;
11128
+ return [...vector2];
11129
+ }
11130
+ function normalized(vector2) {
11131
+ const norm = Math.hypot(...vector2) || 1;
11132
+ return vector2.map((x) => x / norm);
11133
+ }
11134
+ function fitChunks(chunks3) {
11135
+ const usable = chunks3.map((c) => ({ ...c, vector: acceptedVector(c.vector) }));
11136
+ if (usable.length === 0 || usable.some((c) => !c.vector)) return void 0;
11137
+ if (usable.length <= maxChunks) return usable;
11138
+ const out = [];
11139
+ for (let g = 0; g < maxChunks; g++) {
11140
+ const group = usable.slice(
11141
+ Math.floor(g * usable.length / maxChunks),
11142
+ Math.floor((g + 1) * usable.length / maxChunks)
10944
11143
  );
10945
- }
10946
- const snapshots = /* @__PURE__ */ new WeakMap();
10947
- const decode = async (value) => {
10948
- const item = memoryRecord(value);
10949
- const decrypt = async (ciphertext) => {
10950
- if (!ciphertextPattern.test(ciphertext))
10951
- throw new Error("Nearby returned unencrypted memory content");
10952
- const plaintext = await options.decrypt(ciphertext);
10953
- if (plaintext === ciphertext) throw new Error("Memory decryption failed");
10954
- return plaintext;
10955
- };
10956
- const decoded = {
10957
- ...item,
10958
- memory: {
10959
- ...item.memory,
10960
- content: item.memory.is_deleted && item.memory.content === "" ? "" : await decrypt(item.memory.content),
10961
- ...item.memory.kind_value !== void 0 && {
10962
- kind_value: await decrypt(item.memory.kind_value)
10963
- }
10964
- }
10965
- };
10966
- return remember(decoded, item);
10967
- };
10968
- const remember = (decoded, item) => {
10969
- snapshots.set(decoded, {
10970
- id: item.memory.memory_id,
10971
- version: item.version,
10972
- content: decoded.memory.content,
10973
- kindValue: decoded.memory.kind_value,
10974
- encryptedContent: item.memory.content,
10975
- encryptedKindValue: item.memory.kind_value
10976
- });
10977
- return decoded;
10978
- };
10979
- const decodeAll = async (values) => {
10980
- const records = values.map(memoryRecord);
10981
- const settled = await Promise.allSettled(records.map(decode));
10982
- const items = [];
10983
- const failed = [];
10984
- settled.forEach((result, i) => {
10985
- if (result.status === "fulfilled") items.push(result.value);
10986
- else
10987
- failed.push({
10988
- memory_id: records[i].memory.memory_id,
10989
- version: records[i].version,
10990
- error: result.reason instanceof Error ? result.reason : new Error(String(result.reason))
10991
- });
11144
+ const dims = group[0].vector.length;
11145
+ const mean = new Array(dims).fill(0);
11146
+ for (const c of group) {
11147
+ const unit = normalized(c.vector);
11148
+ for (let d = 0; d < dims; d++) mean[d] += unit[d];
11149
+ }
11150
+ out.push({
11151
+ vector: normalized(mean),
11152
+ startOffset: group[0].startOffset,
11153
+ endOffset: group[group.length - 1].endOffset
10992
11154
  });
10993
- return { items, failed };
10994
- };
10995
- const decodeCommitted = async (value, write) => {
10996
- const item = memoryRecord(value);
10997
- if (item.memory.memory_id !== write.body.memory.memory_id || item.memory.content !== write.body.memory.content || item.memory.kind_value !== write.body.memory.kind_value)
10998
- return decode(item);
10999
- return remember(
11000
- {
11001
- ...item,
11002
- memory: {
11003
- ...item.memory,
11004
- content: write.content,
11005
- ...write.kindValue !== void 0 && { kind_value: write.kindValue }
11155
+ }
11156
+ return out;
11157
+ }
11158
+
11159
+ // src/lib/chatStore/anuma.ts
11160
+ var ANUMA_CONVERSATION_ID = "anuma-1on1";
11161
+ var maxAttempts = 3;
11162
+ function anumaTurnContent(bubbles) {
11163
+ return bubbles.join("\n\n");
11164
+ }
11165
+ function anumaLastReadSortKey(conversation) {
11166
+ const record2 = conversation && "record" in conversation ? conversation.record : conversation;
11167
+ const state = record2?.data.anuma;
11168
+ if (!isObject(state)) return 0;
11169
+ const sortKey = state.lastReadSortKey;
11170
+ return Number.isSafeInteger(sortKey) && sortKey > 0 ? sortKey : 0;
11171
+ }
11172
+ function conflict(error) {
11173
+ return error instanceof ChatStoreError && error.code === "version_conflict";
11174
+ }
11175
+ function live(snapshot) {
11176
+ if (snapshot?.record.isDeleted)
11177
+ throw new ChatStoreError("Anuma conversation was deleted", 409, "conversation_deleted");
11178
+ return snapshot;
11179
+ }
11180
+ async function ensureAnumaConversation(store, options = {}) {
11181
+ const existing = live(await store.get("conversation", ANUMA_CONVERSATION_ID, options.signal));
11182
+ if (existing) return existing;
11183
+ const at = (options.now ?? Date.now)();
11184
+ const data = plain({ title: "Anuma", lastActivityAt: at, anuma: { lastReadSortKey: 0 } });
11185
+ try {
11186
+ const [created] = await store.putMany(
11187
+ [
11188
+ {
11189
+ expectedVersion: 0,
11190
+ record: {
11191
+ kind: "conversation",
11192
+ id: ANUMA_CONVERSATION_ID,
11193
+ conversationId: "",
11194
+ sortKey: conversationSortKey(data, at),
11195
+ createdAt: at,
11196
+ updatedAt: at,
11197
+ isDeleted: false,
11198
+ data
11199
+ }
11006
11200
  }
11007
- },
11008
- item
11009
- );
11010
- };
11011
- const encryptField2 = async (plaintext) => {
11012
- const ciphertext = await options.encrypt(plaintext);
11013
- if (ciphertext === plaintext || !ciphertextPattern.test(ciphertext))
11014
- throw new Error("Field encryption must return SDK ciphertext; refusing to upload plaintext");
11015
- return ciphertext;
11016
- };
11017
- const memoryPath = (id2) => {
11018
- if (!id2 || id2.length > 128) throw new Error("memory_id must be 1\u2013128 characters");
11019
- return "/memories/" + encodeURIComponent(id2);
11020
- };
11021
- const prepareWrite = async (memory, expectedVersion, signal) => {
11022
- const unknown = Object.keys(memory).filter(
11023
- (key3) => !Object.prototype.hasOwnProperty.call(memoryFields, key3)
11201
+ ],
11202
+ options.signal
11024
11203
  );
11025
- if (unknown.length) {
11026
- throw new RemoteMemoryError(
11027
- `Unknown private-memory fields (${unknown.join(", ")}); refusing to forward them`,
11028
- 400,
11029
- "unknown_field"
11204
+ return created;
11205
+ } catch (error) {
11206
+ if (!conflict(error)) throw error;
11207
+ const winner = live(await store.get("conversation", ANUMA_CONVERSATION_ID, options.signal));
11208
+ if (!winner) throw error;
11209
+ return winner;
11210
+ }
11211
+ }
11212
+ async function markAnumaRead(store, sortKey, options = {}) {
11213
+ if (!Number.isSafeInteger(sortKey) || sortKey < 0)
11214
+ throw new ChatStoreError("Invalid Anuma read marker", 0, "invalid_record");
11215
+ const now = options.now ?? Date.now;
11216
+ for (let attempt = 1; ; attempt++) {
11217
+ const current = await ensureAnumaConversation(store, options);
11218
+ const read = anumaLastReadSortKey(current);
11219
+ if (sortKey <= read) return current;
11220
+ const record2 = current.record;
11221
+ const state = isObject(record2.data.anuma) ? record2.data.anuma : {};
11222
+ const data = plain({ ...record2.data, anuma: { ...state, lastReadSortKey: sortKey } });
11223
+ try {
11224
+ const [written] = await store.putMany(
11225
+ [
11226
+ {
11227
+ expectedVersion: current.version,
11228
+ record: {
11229
+ ...record2,
11230
+ data,
11231
+ sortKey: conversationSortKey(data, record2.createdAt),
11232
+ updatedAt: Math.max(record2.updatedAt, now())
11233
+ }
11234
+ }
11235
+ ],
11236
+ options.signal
11030
11237
  );
11238
+ return written;
11239
+ } catch (error) {
11240
+ if (!conflict(error) || attempt >= maxAttempts) throw error;
11031
11241
  }
11032
- memory = { ...memory, ...memory.embedding && { embedding: [...memory.embedding] } };
11033
- const path = memoryPath(memory.memory_id);
11034
- const snapshot = typeof expectedVersion === "number" ? void 0 : snapshots.get(expectedVersion);
11035
- if (typeof expectedVersion !== "number" && (!snapshot || snapshot.id !== memory.memory_id || snapshot.version !== expectedVersion.version))
11036
- throw new Error(
11037
- "Expected snapshot must be an unmodified version from this persistence instance"
11038
- );
11039
- expectedVersion = typeof expectedVersion === "number" ? expectedVersion : snapshot.version;
11040
- if (!Number.isSafeInteger(expectedVersion) || expectedVersion < 0)
11041
- throw new Error("expectedVersion must be a nonnegative integer");
11042
- const conflict2 = () => new RemoteMemoryError("The memory changed since it was read", 409, "version_conflict");
11043
- let existing;
11044
- if (expectedVersion !== 0 && !snapshot) {
11242
+ }
11243
+ }
11244
+ function countUnreadAnumaTurns(messages, lastReadSortKey) {
11245
+ const answered = Math.max(
11246
+ 0,
11247
+ ...messages.filter((m) => m.role === "user").map((m) => m.messageId)
11248
+ );
11249
+ const floor = Math.max(lastReadSortKey, answered);
11250
+ const turns = /* @__PURE__ */ new Set();
11251
+ for (const m of messages) {
11252
+ if (m.role !== "assistant" || m.messageId <= floor) continue;
11253
+ turns.add(m.anuma?.turnId ? `turn:${m.anuma.turnId}` : `message:${m.uniqueId}`);
11254
+ }
11255
+ return turns.size;
11256
+ }
11257
+
11258
+ // src/lib/db/chat/summaryOperations.ts
11259
+ import { Q as Q4 } from "@nozbe/watermelondb";
11260
+ function createSummaryContext(database, encryption = {}) {
11261
+ return {
11262
+ ...encryption,
11263
+ database,
11264
+ summariesCollection: database.get("conversation_summaries")
11265
+ };
11266
+ }
11267
+ function modelToStored(record2, summary) {
11268
+ return {
11269
+ uniqueId: record2.id,
11270
+ conversationId: record2.conversationId,
11271
+ summary,
11272
+ summarizedUpTo: record2.summarizedUpTo,
11273
+ tokenCount: record2.tokenCount,
11274
+ createdAt: record2.createdAt,
11275
+ updatedAt: record2.updatedAt
11276
+ };
11277
+ }
11278
+ async function getConversationSummaryOp(ctx, conversationId) {
11279
+ const results = await ctx.summariesCollection.query(Q4.where("conversation_id", conversationId)).fetch();
11280
+ if (results.length === 0) return null;
11281
+ const record2 = results[0];
11282
+ const stored2 = modelToStored(record2, record2.summary);
11283
+ if (ctx.walletAddress && isEncrypted(stored2.summary)) {
11284
+ if (ctx.signMessage) {
11045
11285
  try {
11046
- existing = memoryRecord(await request(path, {}, signal));
11286
+ await requestEncryptionKey(ctx.walletAddress, ctx.signMessage, ctx.embeddedWalletSigner);
11047
11287
  } catch (error) {
11048
- if (error instanceof RemoteMemoryError && error.status === 404) throw conflict2();
11049
- throw error;
11288
+ getLogger().warn("Failed to request the encryption key for the summary:", error);
11050
11289
  }
11051
11290
  }
11052
- if (existing && existing.version !== expectedVersion) throw conflict2();
11053
- const decoded = existing ? await decode(existing).catch(() => void 0) : void 0;
11054
- const reusableContent = snapshot?.encryptedContent ?? existing?.memory.content;
11055
- const content = reusableContent && (snapshot?.content ?? decoded?.memory.content) === memory.content ? reusableContent : await encryptField2(memory.content);
11056
- const kindValue = memory.kind_value === void 0 ? void 0 : (snapshot?.kindValue ?? decoded?.memory.kind_value) === memory.kind_value ? snapshot?.encryptedKindValue ?? existing.memory.kind_value : await encryptField2(memory.kind_value);
11057
- return {
11058
- body: {
11059
- expected_version: expectedVersion,
11060
- memory: { ...memory, content, ...kindValue !== void 0 && { kind_value: kindValue } }
11061
- },
11062
- content: memory.content,
11063
- kindValue: memory.kind_value
11064
- };
11065
- };
11066
- const fetchCandidates = async (embedding2, candidateOptions, withCounts) => {
11067
- const limit = candidateOptions.limit ?? 100;
11068
- if (!Number.isInteger(limit) || limit < 1 || limit > 100)
11069
- throw new Error("Candidate limit must be 1\u2013100");
11070
- for (const [key3, max] of candidateListLimits) {
11071
- const values = candidateOptions[key3];
11072
- if (values && values.length > max)
11073
- throw new Error(`Candidate ${key3} accepts at most ${max} entries (got ${values.length})`);
11291
+ stored2.summary = await decryptField(stored2.summary, ctx.walletAddress);
11292
+ } else if (ctx.walletAddress && ctx.signMessage && stored2.summary) {
11293
+ try {
11294
+ const encryptedSummary = await encryptField(
11295
+ stored2.summary,
11296
+ ctx.walletAddress,
11297
+ ctx.signMessage,
11298
+ ctx.embeddedWalletSigner
11299
+ );
11300
+ await ctx.database.write(async () => {
11301
+ const current = await ctx.summariesCollection.query(Q4.where("id", record2.id)).fetch();
11302
+ if (current[0]?.summary === stored2.summary) {
11303
+ await current[0].update((rec) => {
11304
+ rec.summary = encryptedSummary;
11305
+ });
11306
+ }
11307
+ });
11308
+ } catch (error) {
11309
+ getLogger().warn("Failed to encrypt the legacy summary:", error);
11074
11310
  }
11075
- const result = await request(
11076
- "/candidates",
11077
- {
11078
- method: "POST",
11079
- body: JSON.stringify({
11080
- embedding: embedding2,
11081
- limit,
11082
- force_ids: candidateOptions.force_ids ?? [],
11083
- ...withCounts && { with_counts: true },
11084
- ...Object.fromEntries(
11085
- [
11086
- "scopes",
11087
- "memory_ids",
11088
- "embedding_model",
11089
- "strict_model",
11090
- "include_deleted",
11091
- "deleted_only"
11092
- ].filter(
11093
- (key3) => candidateOptions[key3] !== void 0
11094
- ).map((key3) => [key3, candidateOptions[key3]])
11095
- ),
11096
- ...candidateOptions.fact_types !== void 0 && {
11097
- fact_types: candidateOptions.fact_types
11098
- },
11099
- ...candidateOptions.include_archived !== void 0 && {
11100
- include_archived: candidateOptions.include_archived
11101
- },
11102
- ...candidateOptions.include_quarantined !== void 0 && {
11103
- include_quarantined: candidateOptions.include_quarantined
11104
- },
11105
- ...candidateOptions.include_superseded !== void 0 && {
11106
- include_superseded: candidateOptions.include_superseded
11107
- }
11108
- })
11109
- },
11110
- candidateOptions.signal
11111
- );
11112
- if (!record(result) || !Array.isArray(result.items))
11113
- throw new Error("Invalid nearby candidate response");
11114
- return result;
11115
- };
11116
- const fetchQueryPage = async (queryOptions) => {
11117
- const { signal, ...query } = queryOptions;
11118
- if (query.limit !== void 0 && (!Number.isInteger(query.limit) || query.limit < 1 || query.limit > 200))
11119
- throw new Error("Page limit must be 1\u2013200");
11120
- for (const [key3, max] of queryListLimits) {
11121
- const values = query[key3];
11122
- if (values && values.length > max)
11123
- throw new Error(`Query ${key3} accepts at most ${max} entries (got ${values.length})`);
11311
+ }
11312
+ return stored2;
11313
+ }
11314
+ async function upsertConversationSummaryOp(ctx, conversationId, summary, summarizedUpTo, tokenCount) {
11315
+ const revision2 = getConversationRevision(ctx.database, conversationId);
11316
+ let record2;
11317
+ const encryptedSummary = ctx.walletAddress ? await encryptField(summary, ctx.walletAddress, ctx.signMessage, ctx.embeddedWalletSigner) : summary;
11318
+ await ctx.database.write(async () => {
11319
+ assertConversationRevision(ctx.database, conversationId, revision2);
11320
+ const existing = await ctx.summariesCollection.query(Q4.where("conversation_id", conversationId)).fetch();
11321
+ if (existing.length > 0) {
11322
+ record2 = await existing[0].update((rec) => {
11323
+ rec.summary = encryptedSummary;
11324
+ rec.summarizedUpTo = summarizedUpTo;
11325
+ rec.tokenCount = tokenCount;
11326
+ });
11327
+ } else {
11328
+ record2 = await ctx.summariesCollection.create((rec) => {
11329
+ rec.conversationId = conversationId;
11330
+ rec.summary = encryptedSummary;
11331
+ rec.summarizedUpTo = summarizedUpTo;
11332
+ rec.tokenCount = tokenCount;
11333
+ });
11334
+ }
11335
+ });
11336
+ return modelToStored(record2, summary);
11337
+ }
11338
+ async function deleteConversationSummaryOp(ctx, conversationId) {
11339
+ await ctx.database.write(async () => {
11340
+ const existing = await ctx.summariesCollection.query(Q4.where("conversation_id", conversationId)).fetch();
11341
+ for (const record2 of existing) {
11342
+ await record2.destroyPermanently();
11343
+ }
11344
+ });
11345
+ }
11346
+
11347
+ // src/lib/db/conversationMemory/models.ts
11348
+ import { Model as Model3 } from "@nozbe/watermelondb";
11349
+ import { date as date2, field as field2, readonly, text as text4 } from "@nozbe/watermelondb/decorators";
11350
+ var ConversationMemory = class extends Model3 {
11351
+ };
11352
+ ConversationMemory.table = "conversation_memory";
11353
+ ConversationMemory.associations = {};
11354
+ __decorateClass([
11355
+ text4("conversation_id")
11356
+ ], ConversationMemory.prototype, "conversationId", 2);
11357
+ __decorateClass([
11358
+ text4("memory_id")
11359
+ ], ConversationMemory.prototype, "memoryId", 2);
11360
+ __decorateClass([
11361
+ field2("score")
11362
+ ], ConversationMemory.prototype, "score", 2);
11363
+ __decorateClass([
11364
+ readonly,
11365
+ date2("created_at")
11366
+ ], ConversationMemory.prototype, "createdAt", 2);
11367
+
11368
+ // src/lib/chatStore/chatImports.ts
11369
+ var importBatch = 100;
11370
+ var importChunks = 200;
11371
+ var importPlaintextBytes = 39e5;
11372
+ function ms(date10, fallback = 1) {
11373
+ const value = date10?.getTime();
11374
+ return value && Number.isFinite(value) ? Math.max(1, Math.floor(value)) : fallback;
11375
+ }
11376
+ function bytes2(value) {
11377
+ return new TextEncoder().encode(JSON.stringify(value)).length;
11378
+ }
11379
+ function conversationRecord(conversation, messages) {
11380
+ const createdAt = ms(conversation.createdAt);
11381
+ const data = plain({
11382
+ ...contextFields(conversation.context),
11383
+ title: conversation.title,
11384
+ projectId: conversation.projectId || void 0,
11385
+ pinnedAt: conversation.pinnedAt ? ms(conversation.pinnedAt) : void 0,
11386
+ lastActivityAt: Math.max(
11387
+ ms(conversation.updatedAt, createdAt),
11388
+ ...messages.map((m) => ms(m.createdAt))
11389
+ )
11390
+ });
11391
+ return {
11392
+ record: {
11393
+ kind: "conversation",
11394
+ id: conversation.conversationId,
11395
+ conversationId: "",
11396
+ sortKey: conversationSortKey(data, createdAt),
11397
+ createdAt,
11398
+ updatedAt: ms(conversation.updatedAt, createdAt),
11399
+ isDeleted: false,
11400
+ data
11124
11401
  }
11125
- const page = await request(
11126
- "/memories/query",
11127
- {
11128
- method: "POST",
11129
- body: JSON.stringify(
11130
- Object.fromEntries(
11131
- queryFields.filter((key3) => query[key3] !== void 0).map((key3) => [key3, query[key3]])
11132
- )
11133
- )
11134
- },
11135
- signal
11136
- );
11137
- if (!record(page) || !Array.isArray(page.items) || page.next_cursor !== void 0 && typeof page.next_cursor !== "string" || page.changes_cursor !== void 0 && typeof page.changes_cursor !== "string")
11138
- throw new Error("Invalid nearby private-memory page");
11139
- return page;
11140
- };
11141
- const candidateSet = async (embedding2, candidateOptions = {}) => {
11142
- const result = await fetchCandidates(embedding2, candidateOptions, true);
11143
- if (!Number.isSafeInteger(result.total_count) || result.total_count < 0 || !Number.isSafeInteger(result.unavailable_count) || result.unavailable_count < 0)
11144
- throw new Error("Invalid nearby candidate response");
11145
- return {
11146
- ...await decodeAll(result.items),
11147
- total_count: result.total_count,
11148
- unavailable_count: result.unavailable_count
11149
- };
11150
11402
  };
11403
+ }
11404
+ function messageRecord(message) {
11405
+ const {
11406
+ uniqueId: _uniqueId,
11407
+ messageId: _messageId,
11408
+ createdAt: _createdAt,
11409
+ updatedAt: _updatedAt,
11410
+ decryptionStatus: _status,
11411
+ chunks: chunks3,
11412
+ vector: vector2,
11413
+ ...fields
11414
+ } = message;
11415
+ const fitted = chunks3?.length ? fitChunks(chunks3) : void 0;
11416
+ const single = fitted ? void 0 : acceptedVector(vector2);
11417
+ const embeddings2 = fitted?.map((c) => c.vector) ?? (single ? [single] : void 0);
11418
+ const record2 = fitRecord({
11419
+ kind: "message",
11420
+ id: message.uniqueId,
11421
+ conversationId: message.conversationId,
11422
+ sortKey: message.messageId,
11423
+ createdAt: ms(message.createdAt),
11424
+ updatedAt: ms(message.updatedAt, ms(message.createdAt)),
11425
+ isDeleted: false,
11426
+ data: {
11427
+ ...messageData(fields),
11428
+ ...fitted ? { chunkOffsets: fitted.map((c) => [c.startOffset, c.endOffset]) } : {}
11429
+ }
11430
+ });
11151
11431
  return {
11152
- ...createRemoteTopicPersistence(request),
11153
- ...createRemoteWorkPersistence(request),
11154
- supportsRealtimeChanges: account.realtime_changes === true,
11155
- counts: async () => {
11156
- const result = await request("/counts", {});
11157
- if (!record(result) || ["live", "archived", "quarantined"].some(
11158
- (key3) => !Number.isSafeInteger(result[key3]) || result[key3] < 0
11159
- ))
11160
- throw new Error("Invalid nearby memory counts");
11161
- return {
11162
- live: result.live,
11163
- archived: result.archived,
11164
- quarantined: result.quarantined
11165
- };
11166
- },
11167
- temporal: async (window2, limit = 100) => {
11168
- if (!Number.isFinite(window2.start) || !Number.isFinite(window2.end) || window2.end <= window2.start || !Number.isInteger(limit) || limit < 1 || limit > 100)
11169
- throw new Error("Temporal window must have end > start and limit must be 1\u2013100");
11170
- const result = await request("/temporal", {
11171
- method: "POST",
11172
- body: JSON.stringify({ ...window2, limit })
11173
- });
11174
- if (!record(result) || !Array.isArray(result.items) || result.items.some(
11175
- (item) => !record(item) || typeof item.memory_id !== "string" || !item.memory_id || typeof item.score !== "number" || !Number.isFinite(item.score) || item.score <= 0 || item.score > 1
11176
- ))
11177
- throw new Error("Invalid nearby temporal candidates");
11178
- return result.items.map((item) => item.memory_id);
11179
- },
11180
- get: async (id2, signal) => {
11181
- try {
11182
- return await decode(await request(memoryPath(id2), {}, signal));
11183
- } catch (error) {
11184
- if (error instanceof RemoteMemoryError && error.status === 404) return null;
11185
- throw error;
11186
- }
11187
- },
11188
- list: async (listOptions = {}) => {
11189
- const { signal, ...query } = listOptions;
11190
- if (query.limit !== void 0 && (!Number.isInteger(query.limit) || query.limit < 1 || query.limit > 200)) {
11191
- throw new Error("Page limit must be 1\u2013200");
11192
- }
11193
- const params = new URLSearchParams();
11194
- for (const key3 of [
11195
- "cursor",
11196
- "limit",
11197
- "include_deleted",
11198
- "include_archived",
11199
- "include_quarantined",
11200
- "include_superseded",
11201
- "include_embeddings"
11202
- ]) {
11203
- if (query[key3] !== void 0) params.set(key3, String(query[key3]));
11204
- }
11205
- const page = await request("/memories?" + params.toString(), {}, signal);
11206
- if (!record(page) || !Array.isArray(page.items) || page.next_cursor !== void 0 && typeof page.next_cursor !== "string") {
11207
- throw new Error("Invalid nearby private-memory page");
11208
- }
11209
- return {
11210
- ...await decodeAll(page.items),
11211
- ...page.next_cursor !== void 0 && { next_cursor: page.next_cursor }
11212
- };
11213
- },
11214
- put: async (memory, expectedVersion, signal) => {
11215
- const write = await prepareWrite(memory, expectedVersion, signal);
11216
- return decodeCommitted(
11217
- await request(
11218
- memoryPath(write.body.memory.memory_id),
11219
- {
11220
- method: "PUT",
11221
- body: JSON.stringify({ key_id: options.keyId, ...write.body })
11222
- },
11223
- signal
11224
- ),
11225
- write
11226
- );
11227
- },
11228
- putMany: async (writes, signal, leaseToken) => {
11229
- if (writes.length < 1 || writes.length > 50 || new Set(writes.map((w) => w.memory.memory_id)).size !== writes.length)
11230
- throw new Error("Batch must contain 1\u201350 distinct memory ids");
11231
- const prepared = await Promise.all(
11232
- writes.map((w) => prepareWrite(w.memory, w.expectedVersion, signal))
11233
- );
11234
- const result = await request(
11235
- "/memories/batch",
11236
- {
11237
- method: "POST",
11238
- body: JSON.stringify({
11239
- key_id: options.keyId,
11240
- writes: prepared.map((w) => w.body),
11241
- ...leaseToken && { lease_token: leaseToken }
11242
- })
11243
- },
11244
- signal
11245
- );
11246
- if (!record(result) || !Array.isArray(result.items) || result.items.length !== prepared.length)
11247
- throw new Error("Invalid nearby batch response");
11248
- return Promise.all(result.items.map((item, i) => decodeCommitted(item, prepared[i])));
11249
- },
11250
- query: async (queryOptions = {}) => {
11251
- const page = await fetchQueryPage(queryOptions);
11252
- return {
11253
- ...await decodeAll(page.items),
11254
- ...page.next_cursor !== void 0 && { next_cursor: page.next_cursor },
11255
- ...page.changes_cursor !== void 0 && { changes_cursor: page.changes_cursor }
11256
- };
11257
- },
11258
- queryMetadata: async (queryOptions = {}) => {
11259
- const page = await fetchQueryPage(queryOptions);
11260
- return {
11261
- items: page.items.map((item) => {
11262
- const {
11263
- memory: { content: _content, kind_value: _kindValue, ...memory },
11264
- version,
11265
- server_updated_at
11266
- } = memoryRecord(item);
11267
- return { memory, version, server_updated_at };
11268
- }),
11269
- ...page.next_cursor !== void 0 && { next_cursor: page.next_cursor }
11270
- };
11271
- },
11272
- candidateSet,
11273
- candidates: async (embedding2, candidateOptions = {}) => decodeAll((await fetchCandidates(embedding2, candidateOptions, false)).items)
11432
+ record: record2,
11433
+ ...embeddings2 ? { embeddings: embeddings2 } : {},
11434
+ ...embeddings2 && message.embeddingModel ? { embeddingModel: message.embeddingModel } : {}
11274
11435
  };
11275
11436
  }
11437
+ function batches(records) {
11438
+ const out = [];
11439
+ let batch = [];
11440
+ let chunks3 = 0;
11441
+ let size = 0;
11442
+ for (const item of records) {
11443
+ const vectors = item.embeddings?.length ?? 0;
11444
+ const itemSize = bytes2(item.record);
11445
+ if (batch.length > 0 && (batch.length >= importBatch || chunks3 + vectors > importChunks || size + itemSize > importPlaintextBytes)) {
11446
+ out.push(batch);
11447
+ batch = [];
11448
+ chunks3 = 0;
11449
+ size = 0;
11450
+ }
11451
+ batch.push(item);
11452
+ chunks3 += vectors;
11453
+ size += itemSize;
11454
+ }
11455
+ if (batch.length) out.push(batch);
11456
+ return out;
11457
+ }
11276
11458
 
11277
11459
  // src/lib/chatStore/remote.ts
11278
11460
  var kinds = /* @__PURE__ */ new Set(["conversation", "message", "summary", "project", "memory_links", "setting"]);
@@ -20646,7 +20828,7 @@ async function tryConsolidate(trimmed, ctx, options, prepared) {
20646
20828
  content: m.content,
20647
20829
  similarity: m.similarity
20648
20830
  }));
20649
- const { consolidateMemory: doConsolidate } = await import("./consolidate-7QUL3TOE.mjs");
20831
+ const { consolidateMemory: doConsolidate } = await import("./consolidate-QBV3DDHU.mjs");
20650
20832
  const decision = await doConsolidate(trimmed, candidates, consolidateOptions);
20651
20833
  if (decision.action === "create") return decision.fallbackReason ? null : { create: true };
20652
20834
  if (decision.action === "supersede" && decision.content) {
@@ -30018,6 +30200,8 @@ function useChat(options) {
30018
30200
  const requestId = ++requestIdRef.current;
30019
30201
  const superseded = () => requestIdRef.current !== requestId;
30020
30202
  pendingResumeRef.current = null;
30203
+ let liveRound = null;
30204
+ const deferredErrors = [];
30021
30205
  setIsLoading(true);
30022
30206
  try {
30023
30207
  const tokenGetterValidation = validateTokenGetter(getToken);
@@ -30073,7 +30257,7 @@ function useChat(options) {
30073
30257
  };
30074
30258
  messagesWithContext = [toolGuidanceMessage, ...messagesWithContext];
30075
30259
  }
30076
- const result = await runToolLoop({
30260
+ const loopResult = await runToolLoop({
30077
30261
  messages: messagesWithContext,
30078
30262
  model,
30079
30263
  token,
@@ -30097,12 +30281,13 @@ function useChat(options) {
30097
30281
  transport: xhrTransport,
30098
30282
  onStreamMeta: (meta) => {
30099
30283
  const resolvedApiType = resolveApiType(requestApiType ?? defaultApiType, model);
30100
- pendingResumeRef.current = {
30284
+ liveRound = {
30101
30285
  inferenceId: meta.inferenceId,
30102
30286
  apiType: resolvedApiType,
30103
30287
  model,
30104
30288
  conversationId
30105
30289
  };
30290
+ pendingResumeRef.current = liveRound;
30106
30291
  if (onStreamMetaConsumer) {
30107
30292
  try {
30108
30293
  onStreamMetaConsumer({
@@ -30125,16 +30310,28 @@ function useChat(options) {
30125
30310
  if (globalOnThinking) globalOnThinking(chunk);
30126
30311
  },
30127
30312
  onFinish,
30128
- onError,
30313
+ onError: resumable ? (error) => deferredErrors.push(error) : onError,
30129
30314
  onToolCall,
30130
30315
  onServerToolCall,
30131
30316
  onToolCallArgumentsDelta,
30132
- onStepFinish,
30317
+ onStepFinish: (event) => {
30318
+ liveRound = null;
30319
+ onStepFinish?.(event);
30320
+ },
30133
30321
  preProcessors,
30134
30322
  hooks,
30135
30323
  piiRedaction: requestPiiRedaction ?? resolvedPiiRedaction,
30136
30324
  onPiiRedacted
30137
30325
  });
30326
+ const result = resumable && loopResult.error !== null && loopResult.connectionLost && liveRound ? {
30327
+ data: loopResult.data,
30328
+ error: "Request detached",
30329
+ detached: true,
30330
+ resume: liveRound
30331
+ } : loopResult;
30332
+ if (!("detached" in result && result.detached)) {
30333
+ deferredErrors.forEach((error) => onError?.(error));
30334
+ }
30138
30335
  if (superseded()) {
30139
30336
  } else if ("detached" in result && result.detached && result.resume) {
30140
30337
  pendingResumeRef.current = result.resume;
@@ -30178,13 +30375,25 @@ function useChat(options) {
30178
30375
  onStreamMetaConsumer
30179
30376
  ]
30180
30377
  );
30378
+ const getActiveStreams = useCallback3(
30379
+ async (conversationId) => {
30380
+ const getterValidation = validateTokenGetter(getToken);
30381
+ if (!getterValidation.valid) throw new Error(getterValidation.message);
30382
+ const token = await getToken();
30383
+ const tokenValidation = validateToken(token);
30384
+ if (!tokenValidation.valid) throw new Error(tokenValidation.message);
30385
+ return listActiveStreams({ token, baseUrl, conversationId });
30386
+ },
30387
+ [getToken, baseUrl]
30388
+ );
30181
30389
  return {
30182
30390
  isLoading,
30183
30391
  sendMessage,
30184
30392
  stop,
30185
30393
  cancel,
30186
30394
  detach,
30187
- resumeStream: resumeStream2
30395
+ resumeStream: resumeStream2,
30396
+ getActiveStreams
30188
30397
  };
30189
30398
  }
30190
30399
 
@@ -31240,6 +31449,7 @@ function useChatStorage(options) {
31240
31449
  resumable = false,
31241
31450
  onCancelResult,
31242
31451
  onStreamMeta,
31452
+ inbox,
31243
31453
  onToolSelection,
31244
31454
  piiRedaction,
31245
31455
  onPiiRedacted,
@@ -31707,13 +31917,19 @@ function useChatStorage(options) {
31707
31917
  },
31708
31918
  [vaultCtx, memoryStore]
31709
31919
  );
31920
+ const activeReplyIdRef = useRef4(void 0);
31921
+ const handleStreamMeta = useCallback5(
31922
+ (meta) => onStreamMeta?.({ ...meta, assistantMessageId: activeReplyIdRef.current }),
31923
+ [onStreamMeta]
31924
+ );
31710
31925
  const {
31711
31926
  isLoading,
31712
31927
  sendMessage: baseSendMessage,
31713
31928
  stop: baseStop,
31714
31929
  cancel: baseCancel,
31715
31930
  detach,
31716
- resumeStream: baseResumeStream
31931
+ resumeStream: baseResumeStream,
31932
+ getActiveStreams
31717
31933
  } = useChat({
31718
31934
  getToken,
31719
31935
  baseUrl,
@@ -31727,7 +31943,7 @@ function useChatStorage(options) {
31727
31943
  smoothing,
31728
31944
  resumable,
31729
31945
  onCancelResult,
31730
- onStreamMeta,
31946
+ onStreamMeta: onStreamMeta ? handleStreamMeta : void 0,
31731
31947
  piiRedaction: resolvedPiiRedaction,
31732
31948
  onPiiRedacted,
31733
31949
  onServerToolCall,
@@ -31943,10 +32159,13 @@ function useChatStorage(options) {
31943
32159
  getTokenRef.current = getToken;
31944
32160
  const finalizeResumedRow = useCallback5(
31945
32161
  async (ctx, data, wasStopped, responseDuration, viaQueue = false) => {
31946
- const { content, thinking } = extractAssistantText(data);
31947
- let parentMessageId = ctx.userMessageUniqueId;
32162
+ const extracted2 = extractAssistantText(data);
32163
+ const restore = ctx.restoreText ?? ((text16) => text16);
32164
+ const content = restore(extracted2.content);
32165
+ const thinking = extracted2.thinking ? restore(extracted2.thinking) : extracted2.thinking;
32166
+ let parentMessageId = ctx.parentless ? void 0 : ctx.userMessageUniqueId;
31948
32167
  let knownIds = ctx.knownToolCallEventIds;
31949
- if (ctx.convId && (!knownIds || !parentMessageId)) {
32168
+ if (ctx.convId && (!knownIds || !parentMessageId && !ctx.parentless)) {
31950
32169
  let storedMessages = null;
31951
32170
  try {
31952
32171
  storedMessages = await getMessages(ctx.convId);
@@ -31964,7 +32183,7 @@ function useChatStorage(options) {
31964
32183
  }
31965
32184
  }
31966
32185
  }
31967
- if (!parentMessageId) {
32186
+ if (!parentMessageId && !ctx.parentless) {
31968
32187
  for (let i = storedMessages.length - 1; i >= 0; i--) {
31969
32188
  if (storedMessages[i].uniqueId !== ctx.assistantUniqueId) {
31970
32189
  parentMessageId = storedMessages[i].uniqueId;
@@ -32003,7 +32222,15 @@ function useChatStorage(options) {
32003
32222
  toolCallEvents: currentTurnToolCallEvents && currentTurnToolCallEvents.length > 0 ? currentTurnToolCallEvents : void 0,
32004
32223
  uniqueId: ctx.assistantUniqueId
32005
32224
  };
32006
- if (!viaQueue) return backend.upsertMessage(rowOpts);
32225
+ if (wasStopped) {
32226
+ try {
32227
+ const [current] = await backend.getMessagesByIds([rowOpts.uniqueId]);
32228
+ if (current && !current.wasStopped && !current.error) return current;
32229
+ } catch (err) {
32230
+ getLogger().warn("[useChatStorage] Could not check for a stored answer:", err);
32231
+ }
32232
+ }
32233
+ if (!viaQueue) return clearTurnErrors(backend, await backend.upsertMessage(rowOpts));
32007
32234
  const { result } = await writeOrQueue(
32008
32235
  "createMessage",
32009
32236
  rowOpts,
@@ -32618,6 +32845,7 @@ function useChatStorage(options) {
32618
32845
  } catch {
32619
32846
  }
32620
32847
  }
32848
+ activeReplyIdRef.current = resumable ? effectiveAssistantUniqueId : void 0;
32621
32849
  const result = await baseSendMessage({
32622
32850
  messages: messagesToSend,
32623
32851
  model,
@@ -32643,15 +32871,23 @@ function useChatStorage(options) {
32643
32871
  imageModel,
32644
32872
  conversationId: convId,
32645
32873
  piiRedaction: callPiiRedaction,
32646
- headers
32874
+ headers: resumable && effectiveAssistantUniqueId ? {
32875
+ ...headers,
32876
+ "X-Parent-Message-ID": storedUserMessage.uniqueId,
32877
+ "X-Assistant-Message-ID": effectiveAssistantUniqueId
32878
+ } : headers
32647
32879
  });
32880
+ if (activeReplyIdRef.current === effectiveAssistantUniqueId) {
32881
+ activeReplyIdRef.current = void 0;
32882
+ }
32648
32883
  const responseDuration = (Date.now() - startTime) / 1e3;
32649
32884
  const detachedResult = result;
32650
32885
  if (detachedResult.detached) {
32651
32886
  recordToolSetTurn(database, convId, matchedToolSets);
32652
32887
  const rowId = effectiveAssistantUniqueId ?? `msg_${uuidv78()}`;
32888
+ const resume = detachedResult.resume ? { ...detachedResult.resume, assistantMessageId: rowId } : null;
32653
32889
  pendingResumeRef.current = {
32654
- handle: detachedResult.resume ?? null,
32890
+ handle: resume,
32655
32891
  convId,
32656
32892
  userMessageUniqueId: storedUserMessage.uniqueId,
32657
32893
  userMessageQueueId: userMsgQueueId,
@@ -32669,7 +32905,7 @@ function useChatStorage(options) {
32669
32905
  data: detachedResult.data ?? null,
32670
32906
  error: "Request detached",
32671
32907
  detached: true,
32672
- resume: detachedResult.resume ?? null,
32908
+ resume,
32673
32909
  assistantUniqueId: rowId,
32674
32910
  userMessage: storedUserMessage
32675
32911
  };
@@ -32909,7 +33145,7 @@ function useChatStorage(options) {
32909
33145
  handle: handleOverride,
32910
33146
  convId: handleOverride.conversationId ?? currentConversationId ?? "",
32911
33147
  userMessageUniqueId: "",
32912
- assistantUniqueId: `msg_resume_${handleOverride.inferenceId}`,
33148
+ assistantUniqueId: handleOverride.assistantMessageId ?? `msg_resume_${handleOverride.inferenceId}`,
32913
33149
  model: handleOverride.model,
32914
33150
  imageModel: void 0,
32915
33151
  sources: void 0,
@@ -33033,6 +33269,10 @@ function useChatStorage(options) {
33033
33269
  retirementStopRequestedRef.current = true;
33034
33270
  return;
33035
33271
  }
33272
+ const replaying = activeResumeRef.current;
33273
+ if (replaying && !replaying.headless && replaying.context.handle) {
33274
+ baseCancel(replaying.context.handle);
33275
+ }
33036
33276
  const pending = pendingResumeRef.current;
33037
33277
  if (pending && pending.assistantUniqueId && !isResumingRef.current) {
33038
33278
  const finalize = pending;
@@ -33057,13 +33297,90 @@ function useChatStorage(options) {
33057
33297
  })();
33058
33298
  }
33059
33299
  baseStop();
33060
- }, [baseStop, finalizeResumedRow]);
33300
+ }, [baseStop, baseCancel, finalizeResumedRow]);
33301
+ const watchStream = useCallback5(
33302
+ (stream) => baseResumeStream({
33303
+ inferenceId: stream.inferenceId,
33304
+ apiType: stream.apiType,
33305
+ model: stream.model,
33306
+ conversationId: stream.conversationId ?? void 0
33307
+ }),
33308
+ [baseResumeStream]
33309
+ );
33310
+ const registeredInboxKeyRef = useRef4(null);
33311
+ const deliverInbox = useCallback5(async () => {
33312
+ if (!inbox || !walletAddress || !signMessage) return [];
33313
+ const keyPair = await getKeyPair(walletAddress, signMessage, embeddedWalletSigner);
33314
+ const publicKey = await exportInboxPublicKey(keyPair.publicKey);
33315
+ const registered = registeredInboxKeyRef.current;
33316
+ if (registered?.inbox !== inbox || registered.publicKey !== publicKey) {
33317
+ await inbox.putPublicKey(publicKey);
33318
+ registeredInboxKeyRef.current = { inbox, publicKey };
33319
+ }
33320
+ const results = await deliverInboxAnswers({
33321
+ inbox,
33322
+ keyPair,
33323
+ backend,
33324
+ store: async (opened, messageId) => {
33325
+ const { redactor } = resolveCallPii(
33326
+ void 0,
33327
+ piiRedaction,
33328
+ () => getConversationRedactor(opened.conversation_id, nerDetectorRef.current)
33329
+ );
33330
+ const stored2 = await finalizeResumedRow(
33331
+ {
33332
+ handle: null,
33333
+ convId: opened.conversation_id,
33334
+ userMessageUniqueId: opened.parent_message_id ?? "",
33335
+ parentless: opened.parent_message_id === null,
33336
+ restoreText: redactor ? (text16) => redactor.deAnonymize(text16) : void 0,
33337
+ assistantUniqueId: messageId,
33338
+ model: opened.model,
33339
+ imageModel: void 0,
33340
+ sources: void 0,
33341
+ thoughtProcess: void 0,
33342
+ startTime: Date.now(),
33343
+ partialData: null
33344
+ },
33345
+ opened.response,
33346
+ opened.status === "tool_request",
33347
+ 0
33348
+ );
33349
+ void embedMessageAsync(stored2, (text16) => redactor ? redactor.maskText(text16) : text16);
33350
+ return stored2;
33351
+ }
33352
+ });
33353
+ const pending = pendingResumeRef.current;
33354
+ const answered = results.some(
33355
+ (r) => (r.status === "stored" || r.status === "delivered") && r.assistantMessage?.uniqueId === pending?.assistantUniqueId
33356
+ );
33357
+ if (pending && answered) pendingResumeRef.current = null;
33358
+ return results;
33359
+ }, [
33360
+ inbox,
33361
+ walletAddress,
33362
+ signMessage,
33363
+ embeddedWalletSigner,
33364
+ backend,
33365
+ piiRedaction,
33366
+ getConversationRedactor,
33367
+ finalizeResumedRow,
33368
+ embedMessageAsync
33369
+ ]);
33370
+ const detachWithReplyId = useCallback5(() => {
33371
+ const handle = detach();
33372
+ const replyId = activeReplyIdRef.current;
33373
+ return handle && replyId ? { ...handle, assistantMessageId: replyId } : handle;
33374
+ }, [detach]);
33061
33375
  return {
33062
33376
  isLoading,
33063
33377
  sendMessage,
33064
33378
  stop,
33065
- detach,
33379
+ detach: detachWithReplyId,
33066
33380
  resumeStream: resumeStream2,
33381
+ getActiveStreams,
33382
+ watchStream,
33383
+ deliverInbox,
33067
33384
  conversationId: currentConversationId,
33068
33385
  setConversationId: setCurrentConversationId,
33069
33386
  createConversation,
@@ -33212,6 +33529,7 @@ export {
33212
33529
  createVaultChatBackend,
33213
33530
  createVaultEmbeddingCache,
33214
33531
  createVaultFolderOp,
33532
+ createVaultInbox,
33215
33533
  createVaultMemoriesBatchOp,
33216
33534
  createVaultMemoryOp,
33217
33535
  createWalletPaymentTool,
@@ -33236,6 +33554,7 @@ export {
33236
33554
  ensureAnumaConversation,
33237
33555
  ensureDefaultFoldersOp,
33238
33556
  exchangeNotionCode,
33557
+ exportInboxPublicKey,
33239
33558
  extractAndLinkEntitiesForMemoriesOp,
33240
33559
  extractAndRetain,
33241
33560
  extractEntitiesForMemories,
@@ -33272,6 +33591,7 @@ export {
33272
33591
  isEncrypted,
33273
33592
  isPiiRedactor,
33274
33593
  isRerankerAvailable,
33594
+ listActiveStreams,
33275
33595
  loadSettings,
33276
33596
  localChatBackend,
33277
33597
  markAnumaRead,
@@ -33284,6 +33604,7 @@ export {
33284
33604
  moveMemoriesToFolderOp,
33285
33605
  noopLogger,
33286
33606
  onKeyAvailable,
33607
+ openInboxItem,
33287
33608
  parseMedia,
33288
33609
  preEmbedVaultMemories,
33289
33610
  primaryTopic,