@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.
- package/dist/expo/{consolidate-7QUL3TOE.mjs → consolidate-QBV3DDHU.mjs} +1 -1
- package/dist/expo/index.cjs +2493 -2168
- package/dist/expo/index.d.mts +958 -868
- package/dist/expo/index.d.ts +958 -868
- package/dist/expo/index.mjs +2013 -1692
- package/dist/react/index.cjs +115 -85
- package/dist/react/index.d.mts +711 -710
- package/dist/react/index.d.ts +711 -710
- package/dist/react/index.mjs +115 -85
- package/dist/server/index.cjs +19 -13
- package/dist/server/index.d.mts +4 -2
- package/dist/server/index.d.ts +4 -2
- package/dist/server/index.mjs +19 -13
- package/package.json +1 -1
- package/dist/expo/{chunk-4SOUKJD5.mjs → chunk-VNTEHEGI.mjs} +957 -957
package/dist/expo/index.mjs
CHANGED
|
@@ -20,7 +20,7 @@ import {
|
|
|
20
20
|
taskTypeHeader,
|
|
21
21
|
validateEndpointOverride,
|
|
22
22
|
withInternalFlowMarker
|
|
23
|
-
} from "./chunk-
|
|
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
|
|
58
|
-
const data =
|
|
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/
|
|
5092
|
-
var
|
|
5093
|
-
|
|
5094
|
-
|
|
5095
|
-
|
|
5096
|
-
|
|
5097
|
-
|
|
5098
|
-
|
|
5099
|
-
|
|
5100
|
-
|
|
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
|
-
|
|
5105
|
-
|
|
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
|
-
|
|
5138
|
-
|
|
5139
|
-
|
|
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
|
|
5150
|
-
|
|
5151
|
-
|
|
5152
|
-
|
|
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
|
|
5158
|
-
|
|
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
|
|
5199
|
-
|
|
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
|
-
|
|
5210
|
-
|
|
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
|
-
|
|
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
|
|
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/
|
|
5351
|
-
|
|
5352
|
-
|
|
5353
|
-
|
|
5354
|
-
|
|
5355
|
-
|
|
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
|
|
5154
|
+
function createRemoteTopicPersistence(request) {
|
|
5393
5155
|
return {
|
|
5394
|
-
|
|
5395
|
-
|
|
5396
|
-
|
|
5397
|
-
|
|
5398
|
-
|
|
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
|
|
5403
|
-
|
|
5404
|
-
|
|
5405
|
-
|
|
5406
|
-
|
|
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
|
-
|
|
5414
|
-
|
|
5415
|
-
|
|
5416
|
-
|
|
5417
|
-
|
|
5418
|
-
|
|
5419
|
-
|
|
5420
|
-
|
|
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
|
-
|
|
5426
|
-
|
|
5427
|
-
|
|
5428
|
-
}
|
|
5429
|
-
function buildCacheEntry(tools, checksum, etag) {
|
|
5214
|
+
|
|
5215
|
+
// src/lib/memory/store/remoteWork.ts
|
|
5216
|
+
function createRemoteWorkPersistence(request) {
|
|
5430
5217
|
return {
|
|
5431
|
-
|
|
5432
|
-
|
|
5433
|
-
|
|
5434
|
-
|
|
5435
|
-
|
|
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
|
|
5439
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
5447
|
-
|
|
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
|
-
|
|
5463
|
-
|
|
5464
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
|
8475
|
+
throw new ProviderStreamError(
|
|
8476
|
+
`${label}${message || "Upstream request failed"}`,
|
|
8477
|
+
code || void 0
|
|
8478
|
+
);
|
|
7596
8479
|
}
|
|
7597
|
-
throw new
|
|
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
|
-
|
|
10307
|
-
|
|
10308
|
-
|
|
10309
|
-
|
|
10310
|
-
|
|
10311
|
-
|
|
10312
|
-
|
|
10313
|
-
|
|
10314
|
-
|
|
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
|
-
|
|
10339
|
-
|
|
10340
|
-
|
|
10341
|
-
|
|
10342
|
-
)
|
|
10343
|
-
|
|
10344
|
-
|
|
10345
|
-
|
|
10346
|
-
|
|
10347
|
-
|
|
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
|
-
|
|
10350
|
-
}
|
|
10841
|
+
};
|
|
10351
10842
|
|
|
10352
|
-
// src/lib/db/
|
|
10353
|
-
import { Q as
|
|
10354
|
-
|
|
10355
|
-
|
|
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:
|
|
10364
|
-
conversationId:
|
|
10365
|
-
|
|
10366
|
-
|
|
10367
|
-
|
|
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
|
|
10373
|
-
|
|
10374
|
-
|
|
10375
|
-
|
|
10376
|
-
|
|
10377
|
-
|
|
10378
|
-
|
|
10379
|
-
|
|
10380
|
-
|
|
10381
|
-
|
|
10382
|
-
|
|
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
|
-
|
|
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
|
|
10409
|
-
const
|
|
10410
|
-
|
|
10411
|
-
|
|
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
|
-
|
|
10414
|
-
const
|
|
10415
|
-
|
|
10416
|
-
|
|
10417
|
-
|
|
10418
|
-
|
|
10419
|
-
|
|
10420
|
-
|
|
10421
|
-
|
|
10422
|
-
|
|
10423
|
-
|
|
10424
|
-
|
|
10425
|
-
|
|
10426
|
-
|
|
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
|
|
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
|
|
10435
|
-
|
|
10436
|
-
|
|
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/
|
|
10442
|
-
|
|
10443
|
-
|
|
10444
|
-
var
|
|
10445
|
-
|
|
10446
|
-
|
|
10447
|
-
|
|
10448
|
-
|
|
10449
|
-
|
|
10450
|
-
|
|
10451
|
-
|
|
10452
|
-
|
|
10453
|
-
|
|
10454
|
-
|
|
10455
|
-
|
|
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
|
|
10471
|
-
return
|
|
10949
|
+
function text3(value) {
|
|
10950
|
+
return typeof value === "string" ? value : void 0;
|
|
10472
10951
|
}
|
|
10473
|
-
function
|
|
10474
|
-
|
|
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
|
|
10499
|
-
|
|
10500
|
-
|
|
10501
|
-
|
|
10502
|
-
|
|
10503
|
-
|
|
10504
|
-
|
|
10505
|
-
|
|
10506
|
-
|
|
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
|
-
|
|
10527
|
-
...
|
|
10528
|
-
...
|
|
10965
|
+
turnId,
|
|
10966
|
+
...bubbles !== void 0 ? { bubbles: [...bubbles] } : {},
|
|
10967
|
+
...origin !== void 0 ? { origin } : {}
|
|
10529
10968
|
};
|
|
10530
10969
|
}
|
|
10531
|
-
function
|
|
10532
|
-
|
|
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
|
-
|
|
10554
|
-
|
|
10555
|
-
|
|
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
|
|
10568
|
-
function
|
|
10569
|
-
return
|
|
10978
|
+
var CONTEXT_FIELD_PREFIX = "context.";
|
|
10979
|
+
function contextField(key3) {
|
|
10980
|
+
return `${CONTEXT_FIELD_PREFIX}${key3}`;
|
|
10570
10981
|
}
|
|
10571
|
-
function
|
|
10572
|
-
|
|
10573
|
-
|
|
10574
|
-
|
|
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
|
|
10579
|
-
|
|
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
|
|
10582
|
-
|
|
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
|
|
10585
|
-
if (
|
|
10586
|
-
|
|
10587
|
-
|
|
10588
|
-
|
|
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
|
-
|
|
10610
|
-
|
|
10611
|
-
|
|
10612
|
-
|
|
10613
|
-
|
|
10614
|
-
|
|
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
|
|
11024
|
+
function toMessage(record2) {
|
|
11025
|
+
const { chunkOffsets: _offsets, truncated: _truncated, anuma: rawAnuma, ...fields } = record2.data;
|
|
11026
|
+
const anuma = readAnumaTurn(rawAnuma);
|
|
10617
11027
|
return {
|
|
10618
|
-
|
|
10619
|
-
|
|
10620
|
-
|
|
10621
|
-
|
|
10622
|
-
|
|
10623
|
-
|
|
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
|
-
|
|
10660
|
-
const
|
|
10661
|
-
|
|
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
|
-
|
|
10678
|
-
|
|
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
|
-
|
|
10681
|
-
|
|
10682
|
-
|
|
10683
|
-
|
|
10684
|
-
|
|
10685
|
-
|
|
10686
|
-
|
|
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
|
-
|
|
10702
|
-
|
|
10703
|
-
|
|
10704
|
-
|
|
10705
|
-
|
|
10706
|
-
const
|
|
10707
|
-
|
|
10708
|
-
|
|
10709
|
-
|
|
10710
|
-
|
|
10711
|
-
|
|
10712
|
-
|
|
10713
|
-
|
|
10714
|
-
|
|
10715
|
-
|
|
10716
|
-
|
|
10717
|
-
|
|
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
|
-
|
|
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
|
|
10734
|
-
return
|
|
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
|
-
|
|
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
|
|
10822
|
-
if (
|
|
10823
|
-
|
|
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
|
-
|
|
10826
|
-
}
|
|
10827
|
-
|
|
10828
|
-
|
|
10829
|
-
|
|
10830
|
-
|
|
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
|
-
|
|
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
|
-
|
|
10928
|
-
if (!
|
|
10929
|
-
|
|
10930
|
-
|
|
10931
|
-
|
|
10932
|
-
|
|
10933
|
-
|
|
10934
|
-
|
|
10935
|
-
|
|
10936
|
-
|
|
10937
|
-
|
|
10938
|
-
}
|
|
10939
|
-
if (
|
|
10940
|
-
|
|
10941
|
-
|
|
10942
|
-
|
|
10943
|
-
|
|
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
|
-
|
|
10947
|
-
|
|
10948
|
-
|
|
10949
|
-
|
|
10950
|
-
|
|
10951
|
-
|
|
10952
|
-
|
|
10953
|
-
|
|
10954
|
-
|
|
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
|
-
|
|
10994
|
-
|
|
10995
|
-
|
|
10996
|
-
|
|
10997
|
-
|
|
10998
|
-
|
|
10999
|
-
|
|
11000
|
-
|
|
11001
|
-
|
|
11002
|
-
|
|
11003
|
-
|
|
11004
|
-
|
|
11005
|
-
|
|
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
|
-
|
|
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
|
-
|
|
11026
|
-
|
|
11027
|
-
|
|
11028
|
-
|
|
11029
|
-
|
|
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
|
-
|
|
11033
|
-
|
|
11034
|
-
|
|
11035
|
-
|
|
11036
|
-
|
|
11037
|
-
|
|
11038
|
-
|
|
11039
|
-
|
|
11040
|
-
|
|
11041
|
-
|
|
11042
|
-
|
|
11043
|
-
|
|
11044
|
-
|
|
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
|
-
|
|
11286
|
+
await requestEncryptionKey(ctx.walletAddress, ctx.signMessage, ctx.embeddedWalletSigner);
|
|
11047
11287
|
} catch (error) {
|
|
11048
|
-
|
|
11049
|
-
throw error;
|
|
11288
|
+
getLogger().warn("Failed to request the encryption key for the summary:", error);
|
|
11050
11289
|
}
|
|
11051
11290
|
}
|
|
11052
|
-
|
|
11053
|
-
|
|
11054
|
-
|
|
11055
|
-
|
|
11056
|
-
|
|
11057
|
-
|
|
11058
|
-
|
|
11059
|
-
|
|
11060
|
-
|
|
11061
|
-
|
|
11062
|
-
|
|
11063
|
-
|
|
11064
|
-
|
|
11065
|
-
|
|
11066
|
-
|
|
11067
|
-
|
|
11068
|
-
|
|
11069
|
-
|
|
11070
|
-
|
|
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
|
-
|
|
11076
|
-
|
|
11077
|
-
|
|
11078
|
-
|
|
11079
|
-
|
|
11080
|
-
|
|
11081
|
-
|
|
11082
|
-
|
|
11083
|
-
|
|
11084
|
-
|
|
11085
|
-
|
|
11086
|
-
|
|
11087
|
-
|
|
11088
|
-
|
|
11089
|
-
|
|
11090
|
-
|
|
11091
|
-
|
|
11092
|
-
|
|
11093
|
-
|
|
11094
|
-
|
|
11095
|
-
|
|
11096
|
-
|
|
11097
|
-
|
|
11098
|
-
|
|
11099
|
-
|
|
11100
|
-
|
|
11101
|
-
|
|
11102
|
-
|
|
11103
|
-
|
|
11104
|
-
|
|
11105
|
-
|
|
11106
|
-
|
|
11107
|
-
|
|
11108
|
-
|
|
11109
|
-
|
|
11110
|
-
|
|
11111
|
-
|
|
11112
|
-
|
|
11113
|
-
|
|
11114
|
-
|
|
11115
|
-
|
|
11116
|
-
|
|
11117
|
-
|
|
11118
|
-
|
|
11119
|
-
|
|
11120
|
-
|
|
11121
|
-
|
|
11122
|
-
|
|
11123
|
-
|
|
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
|
-
|
|
11153
|
-
...
|
|
11154
|
-
|
|
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-
|
|
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
|
|
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
|
-
|
|
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
|
|
31947
|
-
|
|
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 (
|
|
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:
|
|
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
|
|
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,
|