@anuma/sdk 3.0.0 → 3.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -21,7 +21,6 @@ import {
21
21
  createConversationOp,
22
22
  createMemoryVaultSearchTool,
23
23
  createMessageOp,
24
- createSseClient,
25
24
  createSupersedingMemoryOp,
26
25
  createVaultEmbeddingCache,
27
26
  createVaultMemoriesBatchOp,
@@ -44,9 +43,6 @@ import {
44
43
  generateEmbeddings,
45
44
  getAllVaultMemoriesOp,
46
45
  getAllVaultMemoryContentsOp,
47
- getApiV1CreditsBalance,
48
- getApiV1CreditsPacks,
49
- getApiV1Models,
50
46
  getConversationOp,
51
47
  getConversationsByProjectLazyOp,
52
48
  getConversationsLazyOp,
@@ -59,6 +55,7 @@ import {
59
55
  getMessagesOp,
60
56
  getMessagesPageOp,
61
57
  getToolCallEvents,
58
+ getToolsChecksum,
62
59
  getUnfiledVaultMemoriesOp,
63
60
  getVaultMemoryOp,
64
61
  hasEncryptionKey,
@@ -69,7 +66,6 @@ import {
69
66
  makeSyntheticStoredMessage,
70
67
  onClearAllEncryptionState,
71
68
  onKeyAvailable,
72
- postApiV1CreditsPurchase,
73
69
  preEmbedVaultMemories,
74
70
  recall,
75
71
  replaceMemoryEntitiesGuardedOp,
@@ -83,6 +79,7 @@ import {
83
79
  supersedeVaultMemoryOp,
84
80
  updateConversationPinnedOp,
85
81
  updateConversationTitleOp,
82
+ updateMessageChunksOp,
86
83
  updateMessageEmbeddingOp,
87
84
  updateMessageErrorOp,
88
85
  updateVaultMemoryEmbeddingOp,
@@ -90,7 +87,7 @@ import {
90
87
  upsertMessageOp,
91
88
  useEncryption,
92
89
  vaultMemoryToStored
93
- } from "./chunk-IY7NNIYM.mjs";
90
+ } from "./chunk-IP2B74KE.mjs";
94
91
  import {
95
92
  PII_PATTERNS,
96
93
  PiiRedactor,
@@ -111,7 +108,8 @@ import {
111
108
  import {
112
109
  BASE_URL,
113
110
  MCP_R2_DOMAIN,
114
- __decorateClass
111
+ __decorateClass,
112
+ createClientConfig
115
113
  } from "./chunk-GYKJOWEO.mjs";
116
114
 
117
115
  // src/lib/chat/cryptoPriceCentroids.ts
@@ -281,6 +279,141 @@ function createWebSearchPreProcessor(options = {}) {
281
279
  };
282
280
  }
283
281
 
282
+ // src/client/core/serverSentEvents.gen.ts
283
+ var createSseClient = ({
284
+ onRequest,
285
+ onSseError,
286
+ onSseEvent,
287
+ responseTransformer,
288
+ responseValidator,
289
+ sseDefaultRetryDelay,
290
+ sseMaxRetryAttempts,
291
+ sseMaxRetryDelay,
292
+ sseSleepFn,
293
+ url,
294
+ ...options
295
+ }) => {
296
+ let lastEventId;
297
+ const sleep3 = sseSleepFn ?? ((ms) => new Promise((resolve) => setTimeout(resolve, ms)));
298
+ const createStream = async function* () {
299
+ let retryDelay = sseDefaultRetryDelay ?? 3e3;
300
+ let attempt = 0;
301
+ const signal = options.signal ?? new AbortController().signal;
302
+ while (true) {
303
+ if (signal.aborted) break;
304
+ attempt++;
305
+ const headers = options.headers instanceof Headers ? options.headers : new Headers(options.headers);
306
+ if (lastEventId !== void 0) {
307
+ headers.set("Last-Event-ID", lastEventId);
308
+ }
309
+ try {
310
+ const requestInit = {
311
+ redirect: "follow",
312
+ ...options,
313
+ body: options.serializedBody,
314
+ headers,
315
+ signal
316
+ };
317
+ let request = new Request(url, requestInit);
318
+ if (onRequest) {
319
+ request = await onRequest(url, requestInit);
320
+ }
321
+ const _fetch = options.fetch ?? globalThis.fetch;
322
+ const response = await _fetch(request);
323
+ if (!response.ok)
324
+ throw new Error(
325
+ `SSE failed: ${response.status} ${response.statusText}`
326
+ );
327
+ if (!response.body) throw new Error("No body in SSE response");
328
+ const reader = response.body.pipeThrough(new TextDecoderStream()).getReader();
329
+ let buffer = "";
330
+ const abortHandler = () => {
331
+ try {
332
+ reader.cancel();
333
+ } catch {
334
+ }
335
+ };
336
+ signal.addEventListener("abort", abortHandler);
337
+ try {
338
+ while (true) {
339
+ const { done, value } = await reader.read();
340
+ if (done) break;
341
+ buffer += value;
342
+ const chunks = buffer.split("\n\n");
343
+ buffer = chunks.pop() ?? "";
344
+ for (const chunk of chunks) {
345
+ const lines = chunk.split("\n");
346
+ const dataLines = [];
347
+ let eventName;
348
+ for (const line of lines) {
349
+ if (line.startsWith("data:")) {
350
+ dataLines.push(line.replace(/^data:\s*/, ""));
351
+ } else if (line.startsWith("event:")) {
352
+ eventName = line.replace(/^event:\s*/, "");
353
+ } else if (line.startsWith("id:")) {
354
+ lastEventId = line.replace(/^id:\s*/, "");
355
+ } else if (line.startsWith("retry:")) {
356
+ const parsed = Number.parseInt(
357
+ line.replace(/^retry:\s*/, ""),
358
+ 10
359
+ );
360
+ if (!Number.isNaN(parsed)) {
361
+ retryDelay = parsed;
362
+ }
363
+ }
364
+ }
365
+ let data;
366
+ let parsedJson = false;
367
+ if (dataLines.length) {
368
+ const rawData = dataLines.join("\n");
369
+ try {
370
+ data = JSON.parse(rawData);
371
+ parsedJson = true;
372
+ } catch {
373
+ data = rawData;
374
+ }
375
+ }
376
+ if (parsedJson) {
377
+ if (responseValidator) {
378
+ await responseValidator(data);
379
+ }
380
+ if (responseTransformer) {
381
+ data = await responseTransformer(data);
382
+ }
383
+ }
384
+ onSseEvent?.({
385
+ data,
386
+ event: eventName,
387
+ id: lastEventId,
388
+ retry: retryDelay
389
+ });
390
+ if (dataLines.length) {
391
+ yield data;
392
+ }
393
+ }
394
+ }
395
+ } finally {
396
+ signal.removeEventListener("abort", abortHandler);
397
+ reader.releaseLock();
398
+ }
399
+ break;
400
+ } catch (error) {
401
+ onSseError?.(error);
402
+ if (sseMaxRetryAttempts !== void 0 && attempt >= sseMaxRetryAttempts) {
403
+ break;
404
+ }
405
+ const backoff = Math.min(
406
+ retryDelay * 2 ** (attempt - 1),
407
+ sseMaxRetryDelay ?? 3e4
408
+ );
409
+ await sleep3(backoff);
410
+ }
411
+ }
412
+ };
413
+ const stream = createStream();
414
+ return { stream };
415
+ };
416
+
284
417
  // src/lib/chat/runHooks.ts
285
418
  function composeHooks(hooks) {
286
419
  const handlers = hooks.filter((h) => h !== void 0 && h !== null);
@@ -3725,6 +3858,702 @@ var xhrTransport = (options) => {
3725
3858
 
3726
3859
  // src/react/useCredits.ts
3727
3860
  import { useCallback, useEffect, useRef, useState } from "react";
3861
+
3862
+ // src/client/core/bodySerializer.gen.ts
3863
+ var jsonBodySerializer = {
3864
+ bodySerializer: (body) => JSON.stringify(
3865
+ body,
3866
+ (_key, value) => typeof value === "bigint" ? value.toString() : value
3867
+ )
3868
+ };
3869
+
3870
+ // src/client/core/pathSerializer.gen.ts
3871
+ var separatorArrayExplode = (style) => {
3872
+ switch (style) {
3873
+ case "label":
3874
+ return ".";
3875
+ case "matrix":
3876
+ return ";";
3877
+ case "simple":
3878
+ return ",";
3879
+ default:
3880
+ return "&";
3881
+ }
3882
+ };
3883
+ var separatorArrayNoExplode = (style) => {
3884
+ switch (style) {
3885
+ case "form":
3886
+ return ",";
3887
+ case "pipeDelimited":
3888
+ return "|";
3889
+ case "spaceDelimited":
3890
+ return "%20";
3891
+ default:
3892
+ return ",";
3893
+ }
3894
+ };
3895
+ var separatorObjectExplode = (style) => {
3896
+ switch (style) {
3897
+ case "label":
3898
+ return ".";
3899
+ case "matrix":
3900
+ return ";";
3901
+ case "simple":
3902
+ return ",";
3903
+ default:
3904
+ return "&";
3905
+ }
3906
+ };
3907
+ var serializeArrayParam = ({
3908
+ allowReserved,
3909
+ explode,
3910
+ name,
3911
+ style,
3912
+ value
3913
+ }) => {
3914
+ if (!explode) {
3915
+ const joinedValues2 = (allowReserved ? value : value.map((v) => encodeURIComponent(v))).join(separatorArrayNoExplode(style));
3916
+ switch (style) {
3917
+ case "label":
3918
+ return `.${joinedValues2}`;
3919
+ case "matrix":
3920
+ return `;${name}=${joinedValues2}`;
3921
+ case "simple":
3922
+ return joinedValues2;
3923
+ default:
3924
+ return `${name}=${joinedValues2}`;
3925
+ }
3926
+ }
3927
+ const separator = separatorArrayExplode(style);
3928
+ const joinedValues = value.map((v) => {
3929
+ if (style === "label" || style === "simple") {
3930
+ return allowReserved ? v : encodeURIComponent(v);
3931
+ }
3932
+ return serializePrimitiveParam({
3933
+ allowReserved,
3934
+ name,
3935
+ value: v
3936
+ });
3937
+ }).join(separator);
3938
+ return style === "label" || style === "matrix" ? separator + joinedValues : joinedValues;
3939
+ };
3940
+ var serializePrimitiveParam = ({
3941
+ allowReserved,
3942
+ name,
3943
+ value
3944
+ }) => {
3945
+ if (value === void 0 || value === null) {
3946
+ return "";
3947
+ }
3948
+ if (typeof value === "object") {
3949
+ throw new Error(
3950
+ "Deeply-nested arrays/objects aren\u2019t supported. Provide your own `querySerializer()` to handle these."
3951
+ );
3952
+ }
3953
+ return `${name}=${allowReserved ? value : encodeURIComponent(value)}`;
3954
+ };
3955
+ var serializeObjectParam = ({
3956
+ allowReserved,
3957
+ explode,
3958
+ name,
3959
+ style,
3960
+ value,
3961
+ valueOnly
3962
+ }) => {
3963
+ if (value instanceof Date) {
3964
+ return valueOnly ? value.toISOString() : `${name}=${value.toISOString()}`;
3965
+ }
3966
+ if (style !== "deepObject" && !explode) {
3967
+ let values = [];
3968
+ Object.entries(value).forEach(([key, v]) => {
3969
+ values = [
3970
+ ...values,
3971
+ key,
3972
+ allowReserved ? v : encodeURIComponent(v)
3973
+ ];
3974
+ });
3975
+ const joinedValues2 = values.join(",");
3976
+ switch (style) {
3977
+ case "form":
3978
+ return `${name}=${joinedValues2}`;
3979
+ case "label":
3980
+ return `.${joinedValues2}`;
3981
+ case "matrix":
3982
+ return `;${name}=${joinedValues2}`;
3983
+ default:
3984
+ return joinedValues2;
3985
+ }
3986
+ }
3987
+ const separator = separatorObjectExplode(style);
3988
+ const joinedValues = Object.entries(value).map(
3989
+ ([key, v]) => serializePrimitiveParam({
3990
+ allowReserved,
3991
+ name: style === "deepObject" ? `${name}[${key}]` : key,
3992
+ value: v
3993
+ })
3994
+ ).join(separator);
3995
+ return style === "label" || style === "matrix" ? separator + joinedValues : joinedValues;
3996
+ };
3997
+
3998
+ // src/client/core/utils.gen.ts
3999
+ function getValidRequestBody(options) {
4000
+ const hasBody = options.body !== void 0;
4001
+ const isSerializedBody = hasBody && options.bodySerializer;
4002
+ if (isSerializedBody) {
4003
+ if ("serializedBody" in options) {
4004
+ const hasSerializedBody = options.serializedBody !== void 0 && options.serializedBody !== "";
4005
+ return hasSerializedBody ? options.serializedBody : null;
4006
+ }
4007
+ return options.body !== "" ? options.body : null;
4008
+ }
4009
+ if (hasBody) {
4010
+ return options.body;
4011
+ }
4012
+ return void 0;
4013
+ }
4014
+
4015
+ // src/client/core/auth.gen.ts
4016
+ var getAuthToken = async (auth, callback) => {
4017
+ const token = typeof callback === "function" ? await callback(auth) : callback;
4018
+ if (!token) {
4019
+ return;
4020
+ }
4021
+ if (auth.scheme === "bearer") {
4022
+ return `Bearer ${token}`;
4023
+ }
4024
+ if (auth.scheme === "basic") {
4025
+ return `Basic ${btoa(token)}`;
4026
+ }
4027
+ return token;
4028
+ };
4029
+
4030
+ // src/client/client/utils.gen.ts
4031
+ var PATH_PARAM_RE = /\{[^{}]+\}/g;
4032
+ var defaultPathSerializer = ({ path, url: _url }) => {
4033
+ let url = _url;
4034
+ const matches = _url.match(PATH_PARAM_RE);
4035
+ if (matches) {
4036
+ for (const match of matches) {
4037
+ let explode = false;
4038
+ let name = match.substring(1, match.length - 1);
4039
+ let style = "simple";
4040
+ if (name.endsWith("*")) {
4041
+ explode = true;
4042
+ name = name.substring(0, name.length - 1);
4043
+ }
4044
+ if (name.startsWith(".")) {
4045
+ name = name.substring(1);
4046
+ style = "label";
4047
+ } else if (name.startsWith(";")) {
4048
+ name = name.substring(1);
4049
+ style = "matrix";
4050
+ }
4051
+ const value = path[name];
4052
+ if (value === void 0 || value === null) {
4053
+ continue;
4054
+ }
4055
+ if (Array.isArray(value)) {
4056
+ url = url.replace(
4057
+ match,
4058
+ serializeArrayParam({ explode, name, style, value })
4059
+ );
4060
+ continue;
4061
+ }
4062
+ if (typeof value === "object") {
4063
+ url = url.replace(
4064
+ match,
4065
+ serializeObjectParam({
4066
+ explode,
4067
+ name,
4068
+ style,
4069
+ value,
4070
+ valueOnly: true
4071
+ })
4072
+ );
4073
+ continue;
4074
+ }
4075
+ if (style === "matrix") {
4076
+ url = url.replace(
4077
+ match,
4078
+ `;${serializePrimitiveParam({
4079
+ name,
4080
+ value
4081
+ })}`
4082
+ );
4083
+ continue;
4084
+ }
4085
+ const replaceValue = encodeURIComponent(
4086
+ style === "label" ? `.${value}` : value
4087
+ );
4088
+ url = url.replace(match, replaceValue);
4089
+ }
4090
+ }
4091
+ return url;
4092
+ };
4093
+ var createQuerySerializer = ({
4094
+ parameters = {},
4095
+ ...args
4096
+ } = {}) => {
4097
+ const querySerializer = (queryParams) => {
4098
+ const search = [];
4099
+ if (queryParams && typeof queryParams === "object") {
4100
+ for (const name in queryParams) {
4101
+ const value = queryParams[name];
4102
+ if (value === void 0 || value === null) {
4103
+ continue;
4104
+ }
4105
+ const options = parameters[name] || args;
4106
+ if (Array.isArray(value)) {
4107
+ const serializedArray = serializeArrayParam({
4108
+ allowReserved: options.allowReserved,
4109
+ explode: true,
4110
+ name,
4111
+ style: "form",
4112
+ value,
4113
+ ...options.array
4114
+ });
4115
+ if (serializedArray) search.push(serializedArray);
4116
+ } else if (typeof value === "object") {
4117
+ const serializedObject = serializeObjectParam({
4118
+ allowReserved: options.allowReserved,
4119
+ explode: true,
4120
+ name,
4121
+ style: "deepObject",
4122
+ value,
4123
+ ...options.object
4124
+ });
4125
+ if (serializedObject) search.push(serializedObject);
4126
+ } else {
4127
+ const serializedPrimitive = serializePrimitiveParam({
4128
+ allowReserved: options.allowReserved,
4129
+ name,
4130
+ value
4131
+ });
4132
+ if (serializedPrimitive) search.push(serializedPrimitive);
4133
+ }
4134
+ }
4135
+ }
4136
+ return search.join("&");
4137
+ };
4138
+ return querySerializer;
4139
+ };
4140
+ var getParseAs = (contentType) => {
4141
+ if (!contentType) {
4142
+ return "stream";
4143
+ }
4144
+ const cleanContent = contentType.split(";")[0]?.trim();
4145
+ if (!cleanContent) {
4146
+ return;
4147
+ }
4148
+ if (cleanContent.startsWith("application/json") || cleanContent.endsWith("+json")) {
4149
+ return "json";
4150
+ }
4151
+ if (cleanContent === "multipart/form-data") {
4152
+ return "formData";
4153
+ }
4154
+ if (["application/", "audio/", "image/", "video/"].some(
4155
+ (type) => cleanContent.startsWith(type)
4156
+ )) {
4157
+ return "blob";
4158
+ }
4159
+ if (cleanContent.startsWith("text/")) {
4160
+ return "text";
4161
+ }
4162
+ return;
4163
+ };
4164
+ var checkForExistence = (options, name) => {
4165
+ if (!name) {
4166
+ return false;
4167
+ }
4168
+ if (options.headers.has(name) || options.query?.[name] || options.headers.get("Cookie")?.includes(`${name}=`)) {
4169
+ return true;
4170
+ }
4171
+ return false;
4172
+ };
4173
+ var setAuthParams = async ({
4174
+ security,
4175
+ ...options
4176
+ }) => {
4177
+ for (const auth of security) {
4178
+ if (checkForExistence(options, auth.name)) {
4179
+ continue;
4180
+ }
4181
+ const token = await getAuthToken(auth, options.auth);
4182
+ if (!token) {
4183
+ continue;
4184
+ }
4185
+ const name = auth.name ?? "Authorization";
4186
+ switch (auth.in) {
4187
+ case "query":
4188
+ if (!options.query) {
4189
+ options.query = {};
4190
+ }
4191
+ options.query[name] = token;
4192
+ break;
4193
+ case "cookie":
4194
+ options.headers.append("Cookie", `${name}=${token}`);
4195
+ break;
4196
+ case "header":
4197
+ default:
4198
+ options.headers.set(name, token);
4199
+ break;
4200
+ }
4201
+ }
4202
+ };
4203
+ var buildUrl = (options) => {
4204
+ const url = getUrl({
4205
+ baseUrl: options.baseUrl,
4206
+ path: options.path,
4207
+ query: options.query,
4208
+ querySerializer: typeof options.querySerializer === "function" ? options.querySerializer : createQuerySerializer(options.querySerializer),
4209
+ url: options.url
4210
+ });
4211
+ return url;
4212
+ };
4213
+ var getUrl = ({
4214
+ baseUrl,
4215
+ path,
4216
+ query,
4217
+ querySerializer,
4218
+ url: _url
4219
+ }) => {
4220
+ const pathUrl = _url.startsWith("/") ? _url : `/${_url}`;
4221
+ let url = (baseUrl ?? "") + pathUrl;
4222
+ if (path) {
4223
+ url = defaultPathSerializer({ path, url });
4224
+ }
4225
+ let search = query ? querySerializer(query) : "";
4226
+ if (search.startsWith("?")) {
4227
+ search = search.substring(1);
4228
+ }
4229
+ if (search) {
4230
+ url += `?${search}`;
4231
+ }
4232
+ return url;
4233
+ };
4234
+ var mergeConfigs = (a, b) => {
4235
+ const config = { ...a, ...b };
4236
+ if (config.baseUrl?.endsWith("/")) {
4237
+ config.baseUrl = config.baseUrl.substring(0, config.baseUrl.length - 1);
4238
+ }
4239
+ config.headers = mergeHeaders(a.headers, b.headers);
4240
+ return config;
4241
+ };
4242
+ var headersEntries = (headers) => {
4243
+ const entries = [];
4244
+ headers.forEach((value, key) => {
4245
+ entries.push([key, value]);
4246
+ });
4247
+ return entries;
4248
+ };
4249
+ var mergeHeaders = (...headers) => {
4250
+ const mergedHeaders = new Headers();
4251
+ for (const header of headers) {
4252
+ if (!header || typeof header !== "object") {
4253
+ continue;
4254
+ }
4255
+ const iterator = header instanceof Headers ? headersEntries(header) : Object.entries(header);
4256
+ for (const [key, value] of iterator) {
4257
+ if (value === null) {
4258
+ mergedHeaders.delete(key);
4259
+ } else if (Array.isArray(value)) {
4260
+ for (const v of value) {
4261
+ mergedHeaders.append(key, v);
4262
+ }
4263
+ } else if (value !== void 0) {
4264
+ mergedHeaders.set(
4265
+ key,
4266
+ typeof value === "object" ? JSON.stringify(value) : value
4267
+ );
4268
+ }
4269
+ }
4270
+ }
4271
+ return mergedHeaders;
4272
+ };
4273
+ var Interceptors = class {
4274
+ constructor() {
4275
+ this.fns = [];
4276
+ }
4277
+ clear() {
4278
+ this.fns = [];
4279
+ }
4280
+ eject(id) {
4281
+ const index = this.getInterceptorIndex(id);
4282
+ if (this.fns[index]) {
4283
+ this.fns[index] = null;
4284
+ }
4285
+ }
4286
+ exists(id) {
4287
+ const index = this.getInterceptorIndex(id);
4288
+ return Boolean(this.fns[index]);
4289
+ }
4290
+ getInterceptorIndex(id) {
4291
+ if (typeof id === "number") {
4292
+ return this.fns[id] ? id : -1;
4293
+ }
4294
+ return this.fns.indexOf(id);
4295
+ }
4296
+ update(id, fn) {
4297
+ const index = this.getInterceptorIndex(id);
4298
+ if (this.fns[index]) {
4299
+ this.fns[index] = fn;
4300
+ return id;
4301
+ }
4302
+ return false;
4303
+ }
4304
+ use(fn) {
4305
+ this.fns.push(fn);
4306
+ return this.fns.length - 1;
4307
+ }
4308
+ };
4309
+ var createInterceptors = () => ({
4310
+ error: new Interceptors(),
4311
+ request: new Interceptors(),
4312
+ response: new Interceptors()
4313
+ });
4314
+ var defaultQuerySerializer = createQuerySerializer({
4315
+ allowReserved: false,
4316
+ array: {
4317
+ explode: true,
4318
+ style: "form"
4319
+ },
4320
+ object: {
4321
+ explode: true,
4322
+ style: "deepObject"
4323
+ }
4324
+ });
4325
+ var defaultHeaders = {
4326
+ "Content-Type": "application/json"
4327
+ };
4328
+ var createConfig = (override = {}) => ({
4329
+ ...jsonBodySerializer,
4330
+ headers: defaultHeaders,
4331
+ parseAs: "auto",
4332
+ querySerializer: defaultQuerySerializer,
4333
+ ...override
4334
+ });
4335
+
4336
+ // src/client/client/client.gen.ts
4337
+ var createClient = (config = {}) => {
4338
+ let _config = mergeConfigs(createConfig(), config);
4339
+ const getConfig = () => ({ ..._config });
4340
+ const setConfig = (config2) => {
4341
+ _config = mergeConfigs(_config, config2);
4342
+ return getConfig();
4343
+ };
4344
+ const interceptors = createInterceptors();
4345
+ const beforeRequest = async (options) => {
4346
+ const opts = {
4347
+ ..._config,
4348
+ ...options,
4349
+ fetch: options.fetch ?? _config.fetch ?? globalThis.fetch,
4350
+ headers: mergeHeaders(_config.headers, options.headers),
4351
+ serializedBody: void 0
4352
+ };
4353
+ if (opts.security) {
4354
+ await setAuthParams({
4355
+ ...opts,
4356
+ security: opts.security
4357
+ });
4358
+ }
4359
+ if (opts.requestValidator) {
4360
+ await opts.requestValidator(opts);
4361
+ }
4362
+ if (opts.body !== void 0 && opts.bodySerializer) {
4363
+ opts.serializedBody = opts.bodySerializer(opts.body);
4364
+ }
4365
+ if (opts.body === void 0 || opts.serializedBody === "") {
4366
+ opts.headers.delete("Content-Type");
4367
+ }
4368
+ const url = buildUrl(opts);
4369
+ return { opts, url };
4370
+ };
4371
+ const request = async (options) => {
4372
+ const { opts, url } = await beforeRequest(options);
4373
+ for (const fn of interceptors.request.fns) {
4374
+ if (fn) {
4375
+ await fn(opts);
4376
+ }
4377
+ }
4378
+ const _fetch = opts.fetch;
4379
+ const requestInit = {
4380
+ ...opts,
4381
+ body: getValidRequestBody(opts)
4382
+ };
4383
+ let response = await _fetch(url, requestInit);
4384
+ for (const fn of interceptors.response.fns) {
4385
+ if (fn) {
4386
+ response = await fn(response, opts);
4387
+ }
4388
+ }
4389
+ const result = {
4390
+ response
4391
+ };
4392
+ if (response.ok) {
4393
+ const parseAs = (opts.parseAs === "auto" ? getParseAs(response.headers.get("Content-Type")) : opts.parseAs) ?? "json";
4394
+ if (response.status === 204 || response.headers.get("Content-Length") === "0") {
4395
+ let emptyData;
4396
+ switch (parseAs) {
4397
+ case "arrayBuffer":
4398
+ case "blob":
4399
+ case "text":
4400
+ emptyData = await response[parseAs]();
4401
+ break;
4402
+ case "formData":
4403
+ emptyData = new FormData();
4404
+ break;
4405
+ case "stream":
4406
+ emptyData = response.body;
4407
+ break;
4408
+ case "json":
4409
+ default:
4410
+ emptyData = {};
4411
+ break;
4412
+ }
4413
+ return {
4414
+ data: emptyData,
4415
+ ...result
4416
+ };
4417
+ }
4418
+ let data;
4419
+ switch (parseAs) {
4420
+ case "arrayBuffer":
4421
+ case "blob":
4422
+ case "formData":
4423
+ case "json":
4424
+ case "text":
4425
+ data = await response[parseAs]();
4426
+ break;
4427
+ case "stream":
4428
+ return {
4429
+ data: response.body,
4430
+ ...result
4431
+ };
4432
+ }
4433
+ if (parseAs === "json") {
4434
+ if (opts.responseValidator) {
4435
+ await opts.responseValidator(data);
4436
+ }
4437
+ if (opts.responseTransformer) {
4438
+ data = await opts.responseTransformer(data);
4439
+ }
4440
+ }
4441
+ return {
4442
+ data,
4443
+ ...result
4444
+ };
4445
+ }
4446
+ const textError = await response.text();
4447
+ let jsonError;
4448
+ try {
4449
+ jsonError = JSON.parse(textError);
4450
+ } catch {
4451
+ }
4452
+ const error = jsonError ?? textError;
4453
+ let finalError = error;
4454
+ for (const fn of interceptors.error.fns) {
4455
+ if (fn) {
4456
+ finalError = await fn(error, response, opts);
4457
+ }
4458
+ }
4459
+ finalError = finalError || {};
4460
+ if (opts.throwOnError) {
4461
+ throw finalError;
4462
+ }
4463
+ return {
4464
+ error: finalError,
4465
+ ...result
4466
+ };
4467
+ };
4468
+ const makeMethodFn = (method) => (options) => request({ ...options, method });
4469
+ const makeSseFn = (method) => async (options) => {
4470
+ const { opts, url } = await beforeRequest(options);
4471
+ return createSseClient({
4472
+ ...opts,
4473
+ body: opts.body,
4474
+ headers: opts.headers,
4475
+ method,
4476
+ onRequest: async (url2, init) => {
4477
+ let request2 = new Request(url2, init);
4478
+ const requestInit = {
4479
+ ...init,
4480
+ method: init.method,
4481
+ url: url2
4482
+ };
4483
+ for (const fn of interceptors.request.fns) {
4484
+ if (fn) {
4485
+ await fn(requestInit);
4486
+ request2 = new Request(requestInit.url, requestInit);
4487
+ }
4488
+ }
4489
+ return request2;
4490
+ },
4491
+ url
4492
+ });
4493
+ };
4494
+ return {
4495
+ buildUrl,
4496
+ connect: makeMethodFn("CONNECT"),
4497
+ delete: makeMethodFn("DELETE"),
4498
+ get: makeMethodFn("GET"),
4499
+ getConfig,
4500
+ head: makeMethodFn("HEAD"),
4501
+ interceptors,
4502
+ options: makeMethodFn("OPTIONS"),
4503
+ patch: makeMethodFn("PATCH"),
4504
+ post: makeMethodFn("POST"),
4505
+ put: makeMethodFn("PUT"),
4506
+ request,
4507
+ setConfig,
4508
+ sse: {
4509
+ connect: makeSseFn("CONNECT"),
4510
+ delete: makeSseFn("DELETE"),
4511
+ get: makeSseFn("GET"),
4512
+ head: makeSseFn("HEAD"),
4513
+ options: makeSseFn("OPTIONS"),
4514
+ patch: makeSseFn("PATCH"),
4515
+ post: makeSseFn("POST"),
4516
+ put: makeSseFn("PUT"),
4517
+ trace: makeSseFn("TRACE")
4518
+ },
4519
+ trace: makeMethodFn("TRACE")
4520
+ };
4521
+ };
4522
+
4523
+ // src/client/client.gen.ts
4524
+ var client = createClient(createClientConfig(createConfig()));
4525
+
4526
+ // src/client/sdk.gen.ts
4527
+ var getApiV1CreditsBalance = (options) => {
4528
+ return (options?.client ?? client).get({
4529
+ url: "/api/v1/credits/balance",
4530
+ ...options
4531
+ });
4532
+ };
4533
+ var getApiV1CreditsPacks = (options) => {
4534
+ return (options?.client ?? client).get({
4535
+ url: "/api/v1/credits/packs",
4536
+ ...options
4537
+ });
4538
+ };
4539
+ var postApiV1CreditsPurchase = (options) => {
4540
+ return (options.client ?? client).post({
4541
+ url: "/api/v1/credits/purchase",
4542
+ ...options,
4543
+ headers: {
4544
+ "Content-Type": "application/json",
4545
+ ...options.headers
4546
+ }
4547
+ });
4548
+ };
4549
+ var getApiV1Models = (options) => {
4550
+ return (options?.client ?? client).get({
4551
+ url: "/api/v1/models",
4552
+ ...options
4553
+ });
4554
+ };
4555
+
4556
+ // src/react/useCredits.ts
3728
4557
  function useCredits(options = {}) {
3729
4558
  const { getToken, baseUrl = BASE_URL, autoFetch = true, onError } = options;
3730
4559
  const [balance, setBalance] = useState(null);
@@ -4116,6 +4945,7 @@ function useChat(options) {
4116
4945
  memoryContext,
4117
4946
  searchContext,
4118
4947
  fileContext,
4948
+ toolGuidance,
4119
4949
  // Responses API options
4120
4950
  temperature,
4121
4951
  maxOutputTokens,
@@ -4187,6 +5017,13 @@ function useChat(options) {
4187
5017
  };
4188
5018
  messagesWithContext = [fileSystemMessage, ...messagesWithContext];
4189
5019
  }
5020
+ if (toolGuidance) {
5021
+ const toolGuidanceMessage = {
5022
+ role: "system",
5023
+ content: [{ type: "text", text: toolGuidance }]
5024
+ };
5025
+ messagesWithContext = [toolGuidanceMessage, ...messagesWithContext];
5026
+ }
4190
5027
  const result = await runToolLoop({
4191
5028
  messages: messagesWithContext,
4192
5029
  model,
@@ -6688,9 +7525,11 @@ function createChunkVectorCache(maxSize = DEFAULT_CHUNK_CACHE_SIZE) {
6688
7525
  return new LRUCache(maxSize);
6689
7526
  }
6690
7527
 
6691
- // src/lib/memory/recallTool.ts
7528
+ // src/lib/memory/recallConstants.ts
6692
7529
  var RECALL_TOOL_NAME = "recall_memory";
6693
7530
  var RECALL_MAX_LIMIT = 50;
7531
+
7532
+ // src/lib/memory/recallTool.ts
6694
7533
  var DEFAULT_LIMIT = 8;
6695
7534
  var DEFAULT_BUDGET = "low";
6696
7535
  function formatEventTime(start, end, kind) {
@@ -7452,10 +8291,116 @@ function createMemoryVaultTool(vaultCtx, options, embeddingOptions, cache) {
7452
8291
  };
7453
8292
  }
7454
8293
 
8294
+ // src/tools/appBuilderPrompt.ts
8295
+ 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.
8296
+
8297
+ 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.
8298
+
8299
+ 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.
8300
+
8301
+ 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 logic, data, or behavior-only change, skip the brief and just make the change.
8302
+
8303
+ WORKFLOW:
8304
+ 1. NEVER output code as text or markdown. ALWAYS use tools.
8305
+ 2. Initial build: state your design brief, then create_file with a "files" array writing ALL files in one call. The preview renders automatically.
8306
+ 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.
8307
+ 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.
8308
+ 5. After substantial changes, call critique_design first (taste, hierarchy, distinctiveness, weakest decision) and then audit_design (mechanical: tokens, focus states, aria-labels, alt, heading order). Patch what each surfaces. critique catches taste issues no checklist would; audit catches mechanics you'd miss. Run both; act on both.
8309
+ 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 \u2014 audit + critique are your feedback in that case.
8310
+ 7. Keep responses to a sentence or two. Exception: when responding to critique_design, write a substantive paragraph per rubric question.
8311
+
8312
+ STRUCTURE:
8313
+ - 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.
8314
+ - App.css: all styles, imported in App.js. No inline style objects.
8315
+ - package.json: list ALL imported packages including react. No CDN script tags. Versions are auto-pinned.
8316
+ - Do NOT create index.js or index.html (auto-generated).
8317
+
8318
+ STATE & PERSISTENCE:
8319
+ - The code runs in the user's browser \u2014 standard browser APIs available (localStorage, URL hash, Date, fetch).
8320
+ - 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.
8321
+ - 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.
8322
+ - Lift state to App when multiple children need it; keep it local otherwise. Extract non-trivial logic into custom hooks (useTodos, useTimer).
8323
+ - useReducer for state shapes with several interrelated fields (cards + columns + filters + drag state).
8324
+
8325
+ FILE & IMAGE UPLOAD:
8326
+ - Picker: \`<input type="file" accept="image/*" multiple>\` (vary accept by use case: \`.csv,text/csv\`, \`.txt,.md,text/plain\`, etc.).
8327
+ - Drag-and-drop: call \`e.preventDefault()\` in onDragOver (otherwise drop never fires); read files from \`event.dataTransfer.files\`. Drop and picker should coexist.
8328
+ - Previews: \`<img src={URL.createObjectURL(file)} />\`. Call \`URL.revokeObjectURL\` on unmount or before replacing \u2014 otherwise the browser leaks memory.
8329
+ - 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).
8330
+ - Validate size and type before processing; reject files larger than the app can hold in memory.
8331
+
8332
+ AI CAPABILITIES \u2014 \`window.app.complete(prompt: string): Promise<string>\` is available in this environment. Build for the real call.
8333
+ - 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.
8334
+ - 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.
8335
+ - 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").
8336
+ - 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.
8337
+ - Skip the call only when the answer is genuinely static \u2014 no language or reasoning needed. Cache repeated prompts in component state when sensible.
8338
+
8339
+ CODE QUALITY \u2014 write as a senior engineer would:
8340
+ - Handle edge cases (empty inputs, division by zero, invalid data). Show helpful error states, not crashes.
8341
+ - 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.
8342
+ - Semantic HTML: real headings, labels, buttons (not divs with onClick), form elements.
8343
+ - Accessible: aria-labels on icon buttons, focus-visible styles, keyboard navigable.
8344
+ - Extract into custom hooks or helpers when a component grows past ~80 lines.
8345
+
8346
+ STYLING \u2014 Tailwind is the CHASSIS, custom CSS carries the IDENTITY:
8347
+ - Tailwind (via Play CDN) for layout, spacing, state (hover/focus), responsive prefixes \u2014 uniform structure.
8348
+ - 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.
8349
+ - 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.
8350
+ - Don't pull additional CSS frameworks. Tailwind + custom CSS is enough.
8351
+
8352
+ VISUAL DESIGN \u2014 every app should look like a real product, not a wireframe:
8353
+ - 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.
8354
+ - 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.
8355
+ - 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.
8356
+ - 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.
8357
+ - 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.
8358
+ - Light/dark mode if reasonable: a single CSS variable inversion under \`[data-theme="dark"]\`.
8359
+ - Customization affordance for multi-session apps: small "Tweaks" panel with theme / density / accent. Skip for throwaway demos.
8360
+ - Responsive: Tailwind responsive prefixes (\`sm:\`, \`md:\`, \`lg:\`) and \`@media\` in App.css. Mobile-first.`;
8361
+
8362
+ // src/tools/document/documentBuilderPrompt.ts
8363
+ 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.
8364
+
8365
+ 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.
8366
+
8367
+ 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.
8368
+
8369
+ AUTHORING \u2014 write the "source" as a single @react-pdf/renderer JSX expression (this is react-pdf, NOT HTML and NOT a slide deck):
8370
+ - 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.
8371
+ - 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.
8372
+ - <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>.
8373
+ - 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.).
8374
+ - 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.
8375
+ - 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.
8376
+
8377
+ A minimal example:
8378
+
8379
+ <Document>
8380
+ <Page size="A4" style={{ paddingVertical: 56, paddingHorizontal: 64, fontFamily: "Times-Roman", fontSize: 11, lineHeight: 1.5 }}>
8381
+ <Text style={{ fontSize: 18, fontFamily: "Times-Bold", marginBottom: 16, textAlign: "center" }}>MUTUAL NON-DISCLOSURE AGREEMENT</Text>
8382
+ <Text style={{ marginBottom: 10 }}>This Mutual Non-Disclosure Agreement (the "Agreement") is entered into as of the date of last signature below.</Text>
8383
+ <View style={{ marginTop: 14 }}>
8384
+ <Text style={{ fontFamily: "Times-Bold", marginBottom: 4 }}>1. Confidential Information</Text>
8385
+ <Text>Each party may disclose certain confidential and proprietary information to the other party.</Text>
8386
+ </View>
8387
+ </Page>
8388
+ </Document>
8389
+
8390
+ WORKFLOW:
8391
+ 1. To create or fully restructure a document, call create_document with the complete source.
8392
+ 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.
8393
+ 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.
8394
+ 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.`;
8395
+
7455
8396
  // src/lib/tools/serverTools.ts
7456
8397
  var DEFAULT_CACHE_EXPIRATION_MS = 24 * 60 * 60 * 1e3;
7457
8398
  var SERVER_TOOLS_CACHE_KEY = "sdk_server_tools_cache";
7458
8399
  var CACHE_VERSION = "1.3";
8400
+ var MIN_CONTENT_LENGTH_FOR_TOOLS = 5;
8401
+ var MAX_CLIENT_TOOLS_AFTER_FILTER = 10;
8402
+ var CLIENT_TOOLS_MIN_SIMILARITY = 0.53;
8403
+ var CLIENT_TOOLS_RELEVANCE_RATIO = 0.75;
7459
8404
  function isNewToolFormat(tool) {
7460
8405
  return "schema" in tool && tool.schema !== void 0;
7461
8406
  }
@@ -7515,7 +8460,7 @@ function getCachedServerTools() {
7515
8460
  if (!cached) return null;
7516
8461
  const parsed = JSON.parse(cached);
7517
8462
  if (parsed.version !== CACHE_VERSION) {
7518
- clearServerToolsCache();
8463
+ removeLocalStorageCache();
7519
8464
  return null;
7520
8465
  }
7521
8466
  return parsed;
@@ -7527,24 +8472,46 @@ function isCacheExpired(cache, expirationMs = DEFAULT_CACHE_EXPIRATION_MS) {
7527
8472
  if (!cache) return true;
7528
8473
  return Date.now() - cache.timestamp > expirationMs;
7529
8474
  }
7530
- function cacheServerTools(tools, checksum) {
7531
- if (typeof localStorage === "undefined") return;
7532
- const cacheData = {
8475
+ function buildCacheEntry(tools, checksum) {
8476
+ return {
7533
8477
  tools,
7534
8478
  timestamp: Date.now(),
7535
8479
  version: CACHE_VERSION,
7536
8480
  ...checksum && { checksum }
7537
8481
  };
8482
+ }
8483
+ function writeLocalStorageCache(entry) {
8484
+ if (typeof localStorage === "undefined") return;
7538
8485
  try {
7539
- localStorage.setItem(SERVER_TOOLS_CACHE_KEY, JSON.stringify(cacheData));
8486
+ localStorage.setItem(SERVER_TOOLS_CACHE_KEY, JSON.stringify(entry));
7540
8487
  } catch (error) {
7541
8488
  getLogger().warn("[serverTools] Failed to cache tools:", error);
7542
8489
  }
7543
8490
  }
7544
- function clearServerToolsCache() {
8491
+ function removeLocalStorageCache() {
7545
8492
  if (typeof localStorage === "undefined") return;
7546
8493
  localStorage.removeItem(SERVER_TOOLS_CACHE_KEY);
7547
8494
  }
8495
+ var localStorageToolsCache = {
8496
+ get: getCachedServerTools,
8497
+ set: writeLocalStorageCache,
8498
+ clear: removeLocalStorageCache
8499
+ };
8500
+ function clearServerToolsCache(cache = localStorageToolsCache) {
8501
+ return cache.clear?.();
8502
+ }
8503
+ function getToolsChecksum2(cache = localStorageToolsCache) {
8504
+ const cached = cache.get();
8505
+ return cached instanceof Promise ? cached.then((c) => c?.checksum) : cached?.checksum;
8506
+ }
8507
+ function shouldRefreshTools(responseChecksum, cache = localStorageToolsCache) {
8508
+ if (!responseChecksum) {
8509
+ return false;
8510
+ }
8511
+ const decide = (cachedChecksum2) => !cachedChecksum2 || cachedChecksum2 !== responseChecksum;
8512
+ const cachedChecksum = getToolsChecksum2(cache);
8513
+ return cachedChecksum instanceof Promise ? cachedChecksum.then(decide) : decide(cachedChecksum);
8514
+ }
7548
8515
  async function fetchServerToolsFromApi(baseUrl, token) {
7549
8516
  const response = await fetch(`${baseUrl}/api/v1/tools`, {
7550
8517
  method: "GET",
@@ -7565,9 +8532,23 @@ async function getServerTools(options) {
7565
8532
  cacheExpirationMs = DEFAULT_CACHE_EXPIRATION_MS,
7566
8533
  forceRefresh = false,
7567
8534
  getToken,
7568
- apiKey
8535
+ apiKey,
8536
+ cache = localStorageToolsCache
7569
8537
  } = options;
7570
- const cached = getCachedServerTools();
8538
+ const persistCache = async (entry) => {
8539
+ try {
8540
+ await cache.set(entry);
8541
+ } catch (error) {
8542
+ getLogger().warn("[serverTools] Cache write failed (non-fatal):", error);
8543
+ }
8544
+ };
8545
+ let rawCached = null;
8546
+ try {
8547
+ rawCached = await cache.get();
8548
+ } catch (error) {
8549
+ getLogger().warn("[serverTools] Cache read failed; falling through to server fetch:", error);
8550
+ }
8551
+ const cached = rawCached && rawCached.version === CACHE_VERSION ? rawCached : null;
7571
8552
  const cacheValid = !isCacheExpired(cached, cacheExpirationMs);
7572
8553
  if (cached && cacheValid && !forceRefresh) {
7573
8554
  return cached.tools;
@@ -7588,7 +8569,7 @@ async function getServerTools(options) {
7588
8569
  }
7589
8570
  const data = await response.json();
7590
8571
  const { tools: tools2, checksum: checksum2 } = convertServerToolsResponse(data);
7591
- cacheServerTools(tools2, checksum2);
8572
+ await persistCache(buildCacheEntry(tools2, checksum2));
7592
8573
  return tools2;
7593
8574
  }
7594
8575
  if (!getToken) {
@@ -7601,7 +8582,7 @@ async function getServerTools(options) {
7601
8582
  return cached?.tools ?? [];
7602
8583
  }
7603
8584
  const { tools, checksum } = await fetchServerToolsFromApi(effectiveBaseUrl, token);
7604
- cacheServerTools(tools, checksum);
8585
+ await persistCache(buildCacheEntry(tools, checksum));
7605
8586
  return tools;
7606
8587
  } catch (error) {
7607
8588
  getLogger().error("[serverTools] Failed to fetch server tools:", error);
@@ -7817,6 +8798,158 @@ function scoreTools(promptEmbeddings, tools) {
7817
8798
  }
7818
8799
  return scores;
7819
8800
  }
8801
+ var BUILT_IN_TOOL_SETS = [
8802
+ {
8803
+ name: "app-generation",
8804
+ // Appended to the base prompt (not replacing it) whenever this set
8805
+ // activates, so the App Builder guidance — including the window.app.complete
8806
+ // runtime-AI contract — rides in with the app-gen tools via the same
8807
+ // semantic selection that includes them. Collected by toolSetSystemPrompts.
8808
+ systemPrompt: APP_BUILDER_PROMPT,
8809
+ // Must stay in sync with APP_FILE_TOOL_NAMES in src/tools/appGeneration.ts.
8810
+ // If a new app-gen tool ships there but isn't added here, semantic
8811
+ // selection will exclude it on every request — the model literally
8812
+ // doesn't see it. The quality tools (audit_design / critique_design /
8813
+ // verify_app) are non-obvious to a user prompt ("build me a kanban")
8814
+ // and rely entirely on set expansion to reach the model.
8815
+ members: [
8816
+ "create_file",
8817
+ "patch_file",
8818
+ "delete_file",
8819
+ "read_file",
8820
+ "list_files",
8821
+ "audit_design",
8822
+ "critique_design",
8823
+ "verify_app"
8824
+ ],
8825
+ // Anchors stay limited to the primary entry-point tools. Users phrase
8826
+ // app intent as "build / make / fix" → semantic match on create_file
8827
+ // or patch_file → full set pulled in. The quality tools fire later in
8828
+ // the workflow and don't anchor on their own; including them as
8829
+ // anchors would risk pulling the set in on unrelated "audit my code"
8830
+ // prompts that have nothing to do with app generation.
8831
+ anchors: ["create_file", "patch_file"],
8832
+ // 0.55: above the global filter floor (0.53) so a barely-relevant match
8833
+ // can't pull in the whole toolkit, but no higher — and we tried higher.
8834
+ // create_file scores chitchat ("what's up" ~0.56, "...about programming"
8835
+ // ~0.57) in the SAME band as legitimate app-EDIT prompts: "Edit the app to
8836
+ // change the background…" measured create_file 0.56 on one e2e run. Raising
8837
+ // the floor to 0.575 to exclude the chitchat therefore also stripped the
8838
+ // file tools from real edit requests — leaving the model unable to touch the
8839
+ // app at all. The bands genuinely overlap; there is no clean cut. So we keep
8840
+ // 0.55 (max recall) and rely on two safety nets instead: APP_BUILDER_PROMPT
8841
+ // is conditional (it no-ops unless the user is actually asking for an app),
8842
+ // and very short greetings ("hey") are filtered out earlier by the
8843
+ // MIN_CONTENT_LENGTH_FOR_TOOLS length gate before embeddings even run.
8844
+ anchorMinSimilarity: 0.55
8845
+ },
8846
+ {
8847
+ name: "slides",
8848
+ members: ["plan_deck", "add_slide", "read_slides", "patch_slides"],
8849
+ anchors: ["plan_deck", "patch_slides"],
8850
+ // Match the client-tool floor (0.53). Short colloquial prompts like
8851
+ // "make me a powerpoint about X" score plan_deck around 0.535 — above
8852
+ // the global floor but inside any anchor gap. plan_deck and patch_slides
8853
+ // are specific enough names that 0.53 won't false-positive in practice.
8854
+ anchorMinSimilarity: 0.53
8855
+ },
8856
+ {
8857
+ name: "documents",
8858
+ // Conditional rider (no-ops unless the user actually asks for a document),
8859
+ // attached so the Document Builder guidance rides in with the document
8860
+ // tools via the same semantic selection that includes them.
8861
+ systemPrompt: DOCUMENT_BUILDER_PROMPT,
8862
+ members: ["create_document", "read_document", "patch_document"],
8863
+ // Entry-point tools anchor; read_document rides in via set expansion.
8864
+ anchors: ["create_document", "patch_document"],
8865
+ // 0.53: match the client-tool floor. "draft me a contract", "write a cover
8866
+ // letter", "make a memo" should pull the set in. Like app-gen's create_file,
8867
+ // document intent ("write me X") overlaps generic chitchat in this band, so
8868
+ // the same two safety nets apply: DOCUMENT_BUILDER_PROMPT is conditional and
8869
+ // the MIN_CONTENT_LENGTH_FOR_TOOLS gate drops bare greetings before
8870
+ // embeddings run. Tune empirically against the tool-selection parity suite.
8871
+ anchorMinSimilarity: 0.53
8872
+ },
8873
+ {
8874
+ name: "github",
8875
+ members: ["github_get_authenticated_user", "github_api"],
8876
+ anchors: ["github_api"],
8877
+ anchorMinSimilarity: 0.55
8878
+ },
8879
+ // Connector sets (#587). Connector chat tools belong to no set, so per-message
8880
+ // semantic filtering silently prunes them on vague prompts ("check my email").
8881
+ // Grouping each connector's tools behind a search/action anchor keeps the
8882
+ // whole connector reachable once any one of its tools matches. Mirrors github:
8883
+ // no systemPrompt rider — per-set prompt guidance is a follow-up.
8884
+ {
8885
+ name: "gmail",
8886
+ members: [
8887
+ "gmail_search_messages",
8888
+ "gmail_get_message",
8889
+ "gmail_create_draft",
8890
+ "gmail_send_message"
8891
+ ],
8892
+ anchors: ["gmail_search_messages", "gmail_send_message"],
8893
+ anchorMinSimilarity: 0.53
8894
+ },
8895
+ {
8896
+ name: "google-calendar",
8897
+ members: [
8898
+ "google_calendar_list_events",
8899
+ "google_calendar_create_event",
8900
+ "google_calendar_update_event"
8901
+ ],
8902
+ anchors: ["google_calendar_list_events", "google_calendar_create_event"],
8903
+ anchorMinSimilarity: 0.53
8904
+ },
8905
+ {
8906
+ name: "google-drive",
8907
+ members: [
8908
+ "google_drive_search",
8909
+ "google_drive_list_recent",
8910
+ "google_drive_get_content",
8911
+ "google_drive_create_file",
8912
+ "google_drive_update_file"
8913
+ ],
8914
+ anchors: ["google_drive_search", "google_drive_get_content", "google_drive_create_file"],
8915
+ anchorMinSimilarity: 0.53
8916
+ },
8917
+ {
8918
+ name: "notion",
8919
+ members: ["notion-search", "notion-fetch", "notion-create-pages", "notion-update-page"],
8920
+ anchors: ["notion-search", "notion-create-pages"],
8921
+ anchorMinSimilarity: 0.53
8922
+ },
8923
+ {
8924
+ name: "x",
8925
+ members: ["x_get_me", "x_get_my_posts"],
8926
+ anchors: ["x_get_me", "x_get_my_posts"],
8927
+ anchorMinSimilarity: 0.53
8928
+ },
8929
+ {
8930
+ name: "slack",
8931
+ members: [
8932
+ "slack_get_me",
8933
+ "slack_list_channels",
8934
+ "slack_search_messages",
8935
+ "slack_list_users",
8936
+ "slack_get_channel_history",
8937
+ "slack_get_thread_replies",
8938
+ "slack_post_message"
8939
+ ],
8940
+ // search is the primary entry point; list_channels also anchors so "what
8941
+ // channels am I in" reaches the set without going through search. post_message
8942
+ // anchors too so post-only prompts surface the write tool past the cutoff.
8943
+ anchors: ["slack_search_messages", "slack_list_channels", "slack_post_message"],
8944
+ anchorMinSimilarity: 0.53
8945
+ },
8946
+ {
8947
+ name: "dropbox",
8948
+ members: ["dropbox_list_folders", "dropbox_get_file_content", "dropbox_search"],
8949
+ anchors: ["dropbox_list_folders", "dropbox_search"],
8950
+ anchorMinSimilarity: 0.53
8951
+ }
8952
+ ];
7820
8953
  function expandToolSetsAdditive(matchedNames, availableNames, scores, toolSets, activeSetNames) {
7821
8954
  const result = new Set(matchedNames);
7822
8955
  for (const ts of toolSets) {
@@ -7834,6 +8967,35 @@ function expandToolSetsAdditive(matchedNames, availableNames, scores, toolSets,
7834
8967
  }
7835
8968
  return result;
7836
8969
  }
8970
+ function activatedToolSetNames(scores, toolSets = BUILT_IN_TOOL_SETS, activeSetNames) {
8971
+ const activated = /* @__PURE__ */ new Set();
8972
+ for (const ts of toolSets) {
8973
+ if (activeSetNames?.has(ts.name)) {
8974
+ activated.add(ts.name);
8975
+ continue;
8976
+ }
8977
+ const minSim = ts.anchorMinSimilarity ?? 0.6;
8978
+ if (ts.anchors.some((a) => (scores.get(a) ?? 0) >= minSim)) {
8979
+ activated.add(ts.name);
8980
+ }
8981
+ }
8982
+ return activated;
8983
+ }
8984
+ function toolSetSystemPrompts(selectedToolNames, toolSets = BUILT_IN_TOOL_SETS, activatedSetNames) {
8985
+ const selected = selectedToolNames instanceof Set ? selectedToolNames : new Set(selectedToolNames);
8986
+ const prompts = [];
8987
+ const seen = /* @__PURE__ */ new Set();
8988
+ for (const ts of toolSets) {
8989
+ const prompt = ts.systemPrompt;
8990
+ if (!prompt || seen.has(prompt)) continue;
8991
+ const active = activatedSetNames ? activatedSetNames.has(ts.name) : ts.anchors.some((anchor) => selected.has(anchor));
8992
+ if (active) {
8993
+ prompts.push(prompt);
8994
+ seen.add(prompt);
8995
+ }
8996
+ }
8997
+ return prompts;
8998
+ }
7837
8999
  function createServerToolsFilter(options = {}) {
7838
9000
  const exclude = new Set(options.excludeTools ?? []);
7839
9001
  const sets = options.toolSets ?? [];
@@ -7918,10 +9080,128 @@ var defaultServerToolsFilter = createServerToolsFilter({
7918
9080
  toolSets: SERVER_TOOL_DEPENDENCY_SETS
7919
9081
  });
7920
9082
 
9083
+ // src/lib/tools/clientToolSelection.ts
9084
+ function getToolName2(t) {
9085
+ const fn = t.function;
9086
+ return fn?.name || t.name || "";
9087
+ }
9088
+ function getToolDescription(t) {
9089
+ const fn = t.function;
9090
+ return fn?.description || t.description || getToolName2(t);
9091
+ }
9092
+ function computeToolGuidance(selectedServerTools, selectedClientTools, extraToolSets, activatedSetNames) {
9093
+ const names = [
9094
+ ...selectedServerTools.map((t) => t.name),
9095
+ ...(selectedClientTools ?? []).map(getToolName2)
9096
+ ].filter(Boolean);
9097
+ const toolSets = extraToolSets.length > 0 ? [...BUILT_IN_TOOL_SETS, ...extraToolSets] : BUILT_IN_TOOL_SETS;
9098
+ const prompts = toolSetSystemPrompts(names, toolSets, activatedSetNames);
9099
+ return prompts.length > 0 ? prompts.join("\n\n") : void 0;
9100
+ }
9101
+ async function autoFilterClientTools(clientTools, promptEmbeddings, cache, embeddingOptions, extraToolSets = [], activeToolSets = [], noEmbeddingsReason = "short-prompt") {
9102
+ const isMemoryTool = (t) => {
9103
+ const name = getToolName2(t);
9104
+ return name.startsWith("memory_vault_") || name === RECALL_TOOL_NAME;
9105
+ };
9106
+ const alwaysInclude = clientTools.filter(isMemoryTool);
9107
+ const filterCandidates = clientTools.filter((t) => !isMemoryTool(t));
9108
+ if (filterCandidates.length === 0) {
9109
+ return { tools: clientTools, activatedSetNames: /* @__PURE__ */ new Set() };
9110
+ }
9111
+ if (!promptEmbeddings && noEmbeddingsReason === "error") {
9112
+ return { tools: clientTools, activatedSetNames: /* @__PURE__ */ new Set() };
9113
+ }
9114
+ if (!promptEmbeddings) {
9115
+ if (activeToolSets.length === 0) {
9116
+ return { tools: [], activatedSetNames: /* @__PURE__ */ new Set() };
9117
+ }
9118
+ const allSets = extraToolSets.length > 0 ? [...BUILT_IN_TOOL_SETS, ...extraToolSets] : BUILT_IN_TOOL_SETS;
9119
+ const activeSets = allSets.filter((s) => activeToolSets.includes(s.name));
9120
+ const stickyMembers = new Set(activeSets.flatMap((s) => s.members));
9121
+ return {
9122
+ tools: [
9123
+ ...alwaysInclude,
9124
+ ...filterCandidates.filter((t) => stickyMembers.has(getToolName2(t)))
9125
+ ],
9126
+ activatedSetNames: new Set(activeSets.map((s) => s.name))
9127
+ };
9128
+ }
9129
+ const toolsNeedingEmbeddings = [];
9130
+ for (const t of filterCandidates) {
9131
+ const name = getToolName2(t);
9132
+ if (name && !cache.has(name)) {
9133
+ toolsNeedingEmbeddings.push({ name, description: getToolDescription(t) });
9134
+ }
9135
+ }
9136
+ if (toolsNeedingEmbeddings.length > 0) {
9137
+ try {
9138
+ const descriptions = toolsNeedingEmbeddings.map((t) => t.description);
9139
+ const embeddings = await generateEmbeddings(descriptions, embeddingOptions);
9140
+ for (let i = 0; i < toolsNeedingEmbeddings.length; i++) {
9141
+ cache.set(toolsNeedingEmbeddings[i].name, embeddings[i]);
9142
+ }
9143
+ } catch {
9144
+ return { tools: clientTools, activatedSetNames: /* @__PURE__ */ new Set() };
9145
+ }
9146
+ }
9147
+ const pseudoServerTools = [];
9148
+ for (const t of filterCandidates) {
9149
+ const name = getToolName2(t);
9150
+ const embedding = cache.get(name);
9151
+ if (!embedding) continue;
9152
+ const fn = t.function;
9153
+ const params = fn?.parameters || fn?.arguments || {
9154
+ type: "object",
9155
+ properties: {},
9156
+ required: []
9157
+ };
9158
+ pseudoServerTools.push({
9159
+ type: "function",
9160
+ name,
9161
+ description: getToolDescription(t),
9162
+ parameters: params,
9163
+ embedding
9164
+ });
9165
+ }
9166
+ const matches = findMatchingTools(promptEmbeddings, pseudoServerTools, {
9167
+ limit: MAX_CLIENT_TOOLS_AFTER_FILTER,
9168
+ minSimilarity: CLIENT_TOOLS_MIN_SIMILARITY,
9169
+ filterAmbiguous: true,
9170
+ // See CLIENT_TOOLS_RELEVANCE_RATIO: 0.75 admits the second intent of a
9171
+ // multi-intent prompt (which lands ~75-80% of the dominant match), while
9172
+ // still trimming the loose tail. The earlier 0.9 made a second intent
9173
+ // structurally unselectable; the anchorMinSimilarity gates (0.53-0.55)
9174
+ // remain the guard against borderline anchors activating a whole set.
9175
+ relevanceRatio: CLIENT_TOOLS_RELEVANCE_RATIO
9176
+ });
9177
+ const matchedNames = new Set(matches.map((m) => m.tool.name));
9178
+ const scores = scoreTools(promptEmbeddings, pseudoServerTools);
9179
+ const availableNames = new Set(filterCandidates.map(getToolName2));
9180
+ const toolSets = extraToolSets.length > 0 ? [...BUILT_IN_TOOL_SETS, ...extraToolSets] : BUILT_IN_TOOL_SETS;
9181
+ const activeSetNames = activeToolSets.length > 0 ? new Set(activeToolSets) : void 0;
9182
+ const finalNames = expandToolSetsAdditive(
9183
+ matchedNames,
9184
+ availableNames,
9185
+ scores,
9186
+ toolSets,
9187
+ activeSetNames
9188
+ );
9189
+ const activatedSetNames = activatedToolSetNames(scores, toolSets, activeSetNames);
9190
+ if (finalNames.size === 0) {
9191
+ return { tools: alwaysInclude, activatedSetNames };
9192
+ }
9193
+ const filtered = filterCandidates.filter((t) => {
9194
+ const name = getToolName2(t);
9195
+ return name && finalNames.has(name);
9196
+ });
9197
+ return { tools: [...alwaysInclude, ...filtered], activatedSetNames };
9198
+ }
9199
+
7921
9200
  // src/expo/useChatStorage.ts
7922
9201
  var CONVERSATION_REDACTOR_LIMIT = 50;
7923
9202
  var NO_CONVERSATION_KEY = "__no_conversation__";
7924
9203
  var conversationRedactors = /* @__PURE__ */ new Map();
9204
+ var clientToolFilterCache = /* @__PURE__ */ new Map();
7925
9205
  function getConversationRedactor(conversationId, nerDetector) {
7926
9206
  const key = conversationId ?? NO_CONVERSATION_KEY;
7927
9207
  const cached = conversationRedactors.get(key);
@@ -8037,6 +9317,8 @@ function useChatStorage(options) {
8037
9317
  getWalletAddress,
8038
9318
  enableQueue = true,
8039
9319
  autoFlushOnKeyAvailable = true,
9320
+ extraToolSets,
9321
+ activeToolSets,
8040
9322
  serverTools: serverToolsConfig,
8041
9323
  autoEmbedMessages = true,
8042
9324
  embeddingModel = DEFAULT_API_EMBEDDING_MODEL,
@@ -8045,6 +9327,7 @@ function useChatStorage(options) {
8045
9327
  resumable = false,
8046
9328
  onCancelResult,
8047
9329
  onStreamMeta,
9330
+ onToolSelection,
8048
9331
  piiRedaction,
8049
9332
  onPiiRedacted,
8050
9333
  nerDetector,
@@ -8259,12 +9542,24 @@ function useChatStorage(options) {
8259
9542
  if (!autoEmbedMessages || !getToken) return;
8260
9543
  if (message.content.length < minContentLength) return;
8261
9544
  try {
8262
- const embedding = await generateEmbedding(mask(message.content), {
8263
- getToken,
8264
- baseUrl,
8265
- model: embeddingModel
8266
- });
8267
- await updateMessageEmbeddingOp(storageCtx, message.uniqueId, embedding, embeddingModel);
9545
+ const opts = { getToken, baseUrl, model: embeddingModel };
9546
+ if (shouldChunkMessage(message.content, DEFAULT_CHUNK_SIZE)) {
9547
+ const textChunks = chunkText(message.content);
9548
+ const embeddings = await generateEmbeddings(
9549
+ textChunks.map((c) => mask(c.text)),
9550
+ opts
9551
+ );
9552
+ const messageChunks = textChunks.map((chunk, i) => ({
9553
+ text: chunk.text,
9554
+ vector: embeddings[i],
9555
+ startOffset: chunk.startOffset,
9556
+ endOffset: chunk.endOffset
9557
+ }));
9558
+ await updateMessageChunksOp(storageCtx, message.uniqueId, messageChunks, embeddingModel);
9559
+ } else {
9560
+ const embedding = await generateEmbedding(mask(message.content), opts);
9561
+ await updateMessageEmbeddingOp(storageCtx, message.uniqueId, embedding, embeddingModel);
9562
+ }
8268
9563
  } catch (err) {
8269
9564
  getLogger().warn("[useChatStorage] Failed to embed message:", err);
8270
9565
  }
@@ -8614,6 +9909,10 @@ function useChatStorage(options) {
8614
9909
  }
8615
9910
  throw new Error("No conversation ID provided and autoCreateConversation is disabled");
8616
9911
  }, [currentConversationId, getConversation, autoCreateConversation, createConversation]);
9912
+ const activeToolSetsRef = useRef4(activeToolSets);
9913
+ activeToolSetsRef.current = activeToolSets;
9914
+ const getTokenRef = useRef4(getToken);
9915
+ getTokenRef.current = getToken;
8617
9916
  const sendMessage = useCallback4(
8618
9917
  async (args) => {
8619
9918
  const {
@@ -8640,6 +9939,7 @@ function useChatStorage(options) {
8640
9939
  maxOutputTokens,
8641
9940
  clientTools,
8642
9941
  serverTools: serverToolsFilter,
9942
+ clientToolsFilter,
8643
9943
  toolChoice,
8644
9944
  reasoning,
8645
9945
  thinking,
@@ -8666,6 +9966,13 @@ function useChatStorage(options) {
8666
9966
  };
8667
9967
  pendingResumeRef.current = null;
8668
9968
  const effectiveAssistantUniqueId = assistantUniqueId ?? (resumable ? `msg_${uuidv73()}` : void 0);
9969
+ const embedToolText = (text12, mask, token) => {
9970
+ const opts = { getToken: token, baseUrl, model: embeddingModel };
9971
+ return shouldChunkMessage(text12, DEFAULT_CHUNK_SIZE) ? generateEmbeddings(
9972
+ chunkText(text12).map((c) => mask(c.text)),
9973
+ opts
9974
+ ) : generateEmbedding(mask(text12), opts);
9975
+ };
8669
9976
  if (walletAddress && signMessage && !hasEncryptionKey(walletAddress)) {
8670
9977
  try {
8671
9978
  await requestEncryptionKey(walletAddress, signMessage, embeddedWalletSigner);
@@ -8676,40 +9983,95 @@ function useChatStorage(options) {
8676
9983
  const effectiveApiType2 = resolveApiType(requestApiType ?? apiType ?? "auto", model);
8677
9984
  const { callPiiRedaction: callPiiRedaction2, maskForCall: maskForCall2 } = resolvePiiForCall(currentConversationId);
8678
9985
  let mergedTools2 = void 0;
8679
- let filteredServerTools = [];
9986
+ let filteredServerTools2 = [];
8680
9987
  const isServerToolsFunction2 = typeof serverToolsFilter === "function";
8681
- if (getToken && effectiveApiType2 === "responses" && !(Array.isArray(serverToolsFilter) && serverToolsFilter.length === 0)) {
9988
+ const extracted2 = extractUserMessageFromMessages(messages);
9989
+ const messageContent = resolveStoredUserContent(
9990
+ storedUserContent,
9991
+ extracted2?.content ?? ""
9992
+ );
9993
+ let skipUserEmbedding;
9994
+ let skipEmbeddingFailed = false;
9995
+ if (getTokenRef.current && effectiveApiType2 === "responses" && !(Array.isArray(serverToolsFilter) && serverToolsFilter.length === 0)) {
8682
9996
  try {
8683
9997
  const allServerTools = await getServerTools({
8684
9998
  baseUrl,
8685
9999
  cacheExpirationMs: serverToolsConfig?.cacheExpirationMs,
8686
- getToken
10000
+ getToken: getTokenRef.current,
10001
+ cache: serverToolsConfig?.cache
8687
10002
  });
8688
- if (isServerToolsFunction2) {
8689
- const extracted2 = extractUserMessageFromMessages(messages);
8690
- const messageContent = resolveStoredUserContent(
8691
- storedUserContent,
8692
- extracted2?.content ?? ""
8693
- );
8694
- if (messageContent.length >= minContentLength) {
8695
- const embedding = await generateEmbedding(maskForCall2(messageContent), {
8696
- getToken,
8697
- baseUrl,
8698
- model: embeddingModel
8699
- });
8700
- const toolNames = serverToolsFilter(embedding, allServerTools);
8701
- filteredServerTools = filterServerTools(allServerTools, toolNames);
8702
- } else {
8703
- filteredServerTools = allServerTools;
10003
+ if (serverToolsConfig?.deferLoading?.enabled) {
10004
+ filteredServerTools2 = allServerTools;
10005
+ } else if (isServerToolsFunction2) {
10006
+ if (messageContent.length >= MIN_CONTENT_LENGTH_FOR_TOOLS) {
10007
+ skipUserEmbedding = await embedToolText(
10008
+ messageContent,
10009
+ maskForCall2,
10010
+ getTokenRef.current
10011
+ );
10012
+ const toolNames = serverToolsFilter(skipUserEmbedding, allServerTools);
10013
+ filteredServerTools2 = filterServerTools(allServerTools, toolNames);
8704
10014
  }
8705
10015
  } else {
8706
- filteredServerTools = filterServerTools(allServerTools, serverToolsFilter);
10016
+ filteredServerTools2 = filterServerTools(allServerTools, serverToolsFilter);
8707
10017
  }
8708
10018
  } catch {
8709
10019
  }
8710
10020
  }
8711
- if (filteredServerTools.length > 0 || clientTools && clientTools.length > 0) {
8712
- mergedTools2 = mergeTools(filteredServerTools, clientTools, effectiveApiType2);
10021
+ let narrowedClientTools2 = clientTools;
10022
+ let clientActivatedSetNames2;
10023
+ if (clientTools?.length) {
10024
+ try {
10025
+ if (!skipUserEmbedding && getTokenRef.current && messageContent.length >= MIN_CONTENT_LENGTH_FOR_TOOLS) {
10026
+ try {
10027
+ skipUserEmbedding = await embedToolText(
10028
+ messageContent,
10029
+ maskForCall2,
10030
+ getTokenRef.current
10031
+ );
10032
+ } catch {
10033
+ skipEmbeddingFailed = true;
10034
+ }
10035
+ }
10036
+ if (typeof clientToolsFilter === "function") {
10037
+ if (!skipEmbeddingFailed) {
10038
+ const keep = new Set(clientToolsFilter(skipUserEmbedding ?? null, clientTools));
10039
+ narrowedClientTools2 = clientTools.filter((t) => keep.has(getToolName2(t)));
10040
+ }
10041
+ } else if (getTokenRef.current) {
10042
+ const { tools: autoTools, activatedSetNames } = await autoFilterClientTools(
10043
+ clientTools,
10044
+ skipUserEmbedding ?? null,
10045
+ clientToolFilterCache,
10046
+ { getToken: getTokenRef.current, baseUrl, model: embeddingModel },
10047
+ extraToolSets ?? [],
10048
+ activeToolSetsRef.current ?? [],
10049
+ skipEmbeddingFailed ? "error" : "short-prompt"
10050
+ );
10051
+ narrowedClientTools2 = autoTools;
10052
+ clientActivatedSetNames2 = activatedSetNames;
10053
+ }
10054
+ } catch (error) {
10055
+ getLogger().warn("[useChatStorage] client tool filtering failed (skipStorage):", error);
10056
+ }
10057
+ }
10058
+ if (filteredServerTools2.length > 0 || narrowedClientTools2 && narrowedClientTools2.length > 0) {
10059
+ mergedTools2 = mergeTools(
10060
+ filteredServerTools2,
10061
+ narrowedClientTools2,
10062
+ effectiveApiType2,
10063
+ serverToolsConfig?.deferLoading
10064
+ );
10065
+ }
10066
+ if (onToolSelection) {
10067
+ try {
10068
+ onToolSelection({
10069
+ prompt: messageContent,
10070
+ clientToolNames: (narrowedClientTools2 ?? []).map(getToolName2).filter(Boolean),
10071
+ serverToolNames: filteredServerTools2.map((t) => t.name)
10072
+ });
10073
+ } catch {
10074
+ }
8713
10075
  }
8714
10076
  const result2 = await baseSendMessage({
8715
10077
  messages,
@@ -8719,6 +10081,12 @@ function useChatStorage(options) {
8719
10081
  memoryContext,
8720
10082
  searchContext,
8721
10083
  fileContext,
10084
+ toolGuidance: computeToolGuidance(
10085
+ filteredServerTools2,
10086
+ narrowedClientTools2,
10087
+ extraToolSets ?? [],
10088
+ clientActivatedSetNames2
10089
+ ),
8722
10090
  temperature,
8723
10091
  maxOutputTokens,
8724
10092
  tools: mergedTools2,
@@ -8746,6 +10114,24 @@ function useChatStorage(options) {
8746
10114
  error: result2.error || "Unknown error"
8747
10115
  };
8748
10116
  }
10117
+ const skipRefreshGetToken = getTokenRef.current;
10118
+ const skipRefreshData = result2.data;
10119
+ if (skipRefreshGetToken) {
10120
+ Promise.resolve().then(
10121
+ () => shouldRefreshTools(getToolsChecksum(skipRefreshData), serverToolsConfig?.cache)
10122
+ ).then((refresh) => {
10123
+ if (refresh) {
10124
+ return getServerTools({
10125
+ baseUrl,
10126
+ getToken: skipRefreshGetToken,
10127
+ forceRefresh: true,
10128
+ cache: serverToolsConfig?.cache
10129
+ });
10130
+ }
10131
+ }).catch((err) => {
10132
+ getLogger().warn("[useChatStorage] Failed to refresh server tools cache:", err);
10133
+ });
10134
+ }
8749
10135
  return {
8750
10136
  data: result2.data,
8751
10137
  error: null,
@@ -8784,12 +10170,12 @@ function useChatStorage(options) {
8784
10170
  if (includeHistory) {
8785
10171
  const validMessages = storedMessages.filter((msg) => !msg.error);
8786
10172
  const limitedMessages = validMessages.slice(-maxHistoryMessages);
8787
- if (summarizeHistory && !getToken) {
10173
+ if (summarizeHistory && !getTokenRef.current) {
8788
10174
  getLogger().warn(
8789
10175
  "[summarize] summarizeHistory is enabled but getToken is not provided \u2014 summarization will be skipped"
8790
10176
  );
8791
10177
  }
8792
- const summaryToken = summarizeHistory && getToken ? await getToken() : null;
10178
+ const summaryToken = summarizeHistory && getTokenRef.current ? await getTokenRef.current() : null;
8793
10179
  const { messagesToConvert, summarySystemMessage } = await maybeSummarizeHistory({
8794
10180
  database,
8795
10181
  conversationId: convId,
@@ -8848,50 +10234,131 @@ function useChatStorage(options) {
8848
10234
  const effectiveApiType = resolveApiType(requestApiType ?? apiType ?? "auto", model);
8849
10235
  let mergedTools = clientTools;
8850
10236
  let userMessageEmbedding;
10237
+ let userMessageEmbeddingFailed = false;
10238
+ let filteredServerTools = [];
8851
10239
  const isServerToolsFunction = typeof serverToolsFilter === "function";
8852
- if (getToken && !(Array.isArray(serverToolsFilter) && serverToolsFilter.length === 0)) {
10240
+ if (getTokenRef.current && !(Array.isArray(serverToolsFilter) && serverToolsFilter.length === 0)) {
8853
10241
  try {
8854
10242
  const allServerTools = await getServerTools({
8855
10243
  baseUrl,
8856
10244
  cacheExpirationMs: serverToolsConfig?.cacheExpirationMs,
8857
- getToken
10245
+ getToken: getTokenRef.current,
10246
+ cache: serverToolsConfig?.cache
8858
10247
  });
8859
- let filteredServerTools;
8860
- if (isServerToolsFunction) {
8861
- if (contentForStorage.length >= minContentLength) {
8862
- userMessageEmbedding = await generateEmbedding(maskForCall(contentForStorage), {
8863
- getToken,
8864
- baseUrl,
8865
- model: embeddingModel
8866
- });
10248
+ if (serverToolsConfig?.deferLoading?.enabled && effectiveApiType === "responses") {
10249
+ filteredServerTools = allServerTools;
10250
+ } else if (isServerToolsFunction) {
10251
+ if (contentForStorage.length >= MIN_CONTENT_LENGTH_FOR_TOOLS) {
10252
+ userMessageEmbedding = await embedToolText(
10253
+ contentForStorage,
10254
+ maskForCall,
10255
+ getTokenRef.current
10256
+ );
8867
10257
  const toolNames = serverToolsFilter(userMessageEmbedding, allServerTools);
8868
10258
  filteredServerTools = filterServerTools(allServerTools, toolNames);
8869
- } else {
8870
- filteredServerTools = allServerTools;
8871
10259
  }
8872
10260
  } else {
8873
10261
  filteredServerTools = filterServerTools(allServerTools, serverToolsFilter);
8874
10262
  }
8875
10263
  if (filteredServerTools.length > 0) {
8876
- mergedTools = mergeTools(filteredServerTools, clientTools, effectiveApiType);
10264
+ mergedTools = mergeTools(
10265
+ filteredServerTools,
10266
+ clientTools,
10267
+ effectiveApiType,
10268
+ serverToolsConfig?.deferLoading
10269
+ );
8877
10270
  }
8878
10271
  } catch (error) {
8879
10272
  getLogger().warn("[useChatStorage] Failed to fetch server tools:", error);
8880
10273
  }
8881
10274
  }
10275
+ let narrowedClientTools = clientTools;
10276
+ let clientActivatedSetNames;
10277
+ if (clientTools?.length) {
10278
+ try {
10279
+ if (!userMessageEmbedding && getTokenRef.current && contentForStorage.length >= MIN_CONTENT_LENGTH_FOR_TOOLS) {
10280
+ try {
10281
+ userMessageEmbedding = await embedToolText(
10282
+ contentForStorage,
10283
+ maskForCall,
10284
+ getTokenRef.current
10285
+ );
10286
+ } catch {
10287
+ userMessageEmbeddingFailed = true;
10288
+ }
10289
+ }
10290
+ if (typeof clientToolsFilter === "function") {
10291
+ if (!userMessageEmbeddingFailed) {
10292
+ const keep = new Set(clientToolsFilter(userMessageEmbedding ?? null, clientTools));
10293
+ narrowedClientTools = clientTools.filter((t) => keep.has(getToolName2(t)));
10294
+ }
10295
+ } else if (getTokenRef.current) {
10296
+ const { tools: autoTools, activatedSetNames } = await autoFilterClientTools(
10297
+ clientTools,
10298
+ userMessageEmbedding ?? null,
10299
+ clientToolFilterCache,
10300
+ { getToken: getTokenRef.current, baseUrl, model: embeddingModel },
10301
+ extraToolSets ?? [],
10302
+ activeToolSetsRef.current ?? [],
10303
+ userMessageEmbeddingFailed ? "error" : "short-prompt"
10304
+ );
10305
+ narrowedClientTools = autoTools;
10306
+ clientActivatedSetNames = activatedSetNames;
10307
+ }
10308
+ mergedTools = filteredServerTools.length > 0 || (narrowedClientTools?.length ?? 0) > 0 ? mergeTools(
10309
+ filteredServerTools,
10310
+ narrowedClientTools,
10311
+ effectiveApiType,
10312
+ serverToolsConfig?.deferLoading
10313
+ ) : void 0;
10314
+ } catch (error) {
10315
+ getLogger().warn("[useChatStorage] client tool filtering failed:", error);
10316
+ }
10317
+ }
8882
10318
  if (!userMsgQueueId) {
8883
10319
  if (userMessageEmbedding && autoEmbedMessages) {
8884
- updateMessageEmbeddingOp(
8885
- storageCtx,
8886
- storedUserMessage.uniqueId,
8887
- userMessageEmbedding,
8888
- embeddingModel
8889
- ).catch(() => {
8890
- });
10320
+ if (Array.isArray(userMessageEmbedding[0])) {
10321
+ const textChunks = chunkText(contentForStorage);
10322
+ const messageChunks = textChunks.map((chunk, i) => ({
10323
+ text: chunk.text,
10324
+ vector: userMessageEmbedding[i],
10325
+ startOffset: chunk.startOffset,
10326
+ endOffset: chunk.endOffset
10327
+ }));
10328
+ updateMessageChunksOp(
10329
+ storageCtx,
10330
+ storedUserMessage.uniqueId,
10331
+ messageChunks,
10332
+ embeddingModel
10333
+ ).catch((err) => {
10334
+ getLogger().warn(
10335
+ "[useChatStorage] Failed to persist chunked embeddings for user message:",
10336
+ err
10337
+ );
10338
+ });
10339
+ } else {
10340
+ updateMessageEmbeddingOp(
10341
+ storageCtx,
10342
+ storedUserMessage.uniqueId,
10343
+ userMessageEmbedding,
10344
+ embeddingModel
10345
+ ).catch(() => {
10346
+ });
10347
+ }
8891
10348
  } else {
8892
10349
  void embedMessageAsync(storedUserMessage, maskForCall);
8893
10350
  }
8894
10351
  }
10352
+ if (onToolSelection) {
10353
+ try {
10354
+ onToolSelection({
10355
+ prompt: contentForStorage,
10356
+ clientToolNames: (narrowedClientTools ?? []).map(getToolName2).filter(Boolean),
10357
+ serverToolNames: filteredServerTools.map((t) => t.name)
10358
+ });
10359
+ } catch {
10360
+ }
10361
+ }
8895
10362
  const result = await baseSendMessage({
8896
10363
  messages: messagesToSend,
8897
10364
  model,
@@ -8900,6 +10367,12 @@ function useChatStorage(options) {
8900
10367
  memoryContext,
8901
10368
  searchContext,
8902
10369
  fileContext,
10370
+ toolGuidance: computeToolGuidance(
10371
+ filteredServerTools,
10372
+ narrowedClientTools,
10373
+ extraToolSets ?? [],
10374
+ clientActivatedSetNames
10375
+ ),
8903
10376
  apiType: requestApiType,
8904
10377
  // Responses API options
8905
10378
  temperature,
@@ -9101,6 +10574,21 @@ function useChatStorage(options) {
9101
10574
  userMessage: storedUserMessage
9102
10575
  };
9103
10576
  }
10577
+ const refreshGetToken = getTokenRef.current;
10578
+ if (refreshGetToken) {
10579
+ Promise.resolve().then(() => shouldRefreshTools(getToolsChecksum(responseData), serverToolsConfig?.cache)).then((refresh) => {
10580
+ if (refresh) {
10581
+ return getServerTools({
10582
+ baseUrl,
10583
+ getToken: refreshGetToken,
10584
+ forceRefresh: true,
10585
+ cache: serverToolsConfig?.cache
10586
+ });
10587
+ }
10588
+ }).catch((err) => {
10589
+ getLogger().warn("[useChatStorage] Failed to refresh server tools cache:", err);
10590
+ });
10591
+ }
9104
10592
  return {
9105
10593
  data: responseData,
9106
10594
  error: null,
@@ -10666,6 +12154,7 @@ async function updateVaultFolderOp(ctx, id, opts) {
10666
12154
  if (record.isDeleted) return null;
10667
12155
  const scopeChanged = opts.scope !== void 0 && opts.scope !== record.scope;
10668
12156
  const updated = await ctx.database.write(async () => {
12157
+ const memories = scopeChanged ? await ctx.vaultMemoryCollection.query(Q5.where("folder_id", id), Q5.where("is_deleted", false)).fetch() : [];
10669
12158
  const updates = [];
10670
12159
  updates.push(
10671
12160
  record.prepareUpdate((r) => {
@@ -10673,15 +12162,12 @@ async function updateVaultFolderOp(ctx, id, opts) {
10673
12162
  if (opts.scope !== void 0) r._setRaw("scope", opts.scope);
10674
12163
  })
10675
12164
  );
10676
- if (scopeChanged) {
10677
- const memories = await ctx.vaultMemoryCollection.query(Q5.where("folder_id", id), Q5.where("is_deleted", false)).fetch();
10678
- for (const memory of memories) {
10679
- updates.push(
10680
- memory.prepareUpdate((r) => {
10681
- r._setRaw("scope", opts.scope);
10682
- })
10683
- );
10684
- }
12165
+ for (const memory of memories) {
12166
+ updates.push(
12167
+ memory.prepareUpdate((r) => {
12168
+ r._setRaw("scope", opts.scope);
12169
+ })
12170
+ );
10685
12171
  }
10686
12172
  await ctx.database.batch(...updates);
10687
12173
  return ctx.vaultFolderCollection.find(id);