mindcache 3.5.2 → 3.6.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/index.js CHANGED
@@ -6,6 +6,8 @@ var decoding = require('lib0/decoding');
6
6
  var Y = require('yjs');
7
7
  var yIndexeddb = require('y-indexeddb');
8
8
  var diff = require('fast-diff');
9
+ var ai = require('ai');
10
+ var zod = require('zod');
9
11
  var react = require('react');
10
12
 
11
13
  function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
@@ -212,7 +214,6 @@ var init_CloudAdapter = __esm({
212
214
  if (!config.baseUrl) {
213
215
  throw new Error("MindCache Cloud: baseUrl is required. Please provide the cloud API URL in your configuration.");
214
216
  }
215
- this.validateConfig(config);
216
217
  this.setupNetworkDetection();
217
218
  }
218
219
  ws = null;
@@ -228,43 +229,6 @@ var init_CloudAdapter = __esm({
228
229
  handleOnline = null;
229
230
  handleOffline = null;
230
231
  _synced = false;
231
- /**
232
- * Validate configuration and warn about common mistakes
233
- */
234
- validateConfig(config) {
235
- const baseUrl = config.baseUrl;
236
- if (!baseUrl) {
237
- return;
238
- }
239
- console.log("\u2601\uFE0F MindCache Cloud Config:", {
240
- baseUrl,
241
- instanceId: config.instanceId,
242
- hasTokenProvider: !!config.tokenProvider,
243
- hasApiKey: !!config.apiKey
244
- });
245
- try {
246
- const url = new URL(baseUrl);
247
- if (url.hostname === "mindcache.dev") {
248
- console.error(
249
- '\u26A0\uFE0F MindCache Cloud WARNING: baseUrl is set to "mindcache.dev" but the API is at "api.mindcache.dev".\n Current: ' + baseUrl + "\n Expected: https://api.mindcache.dev\n This will cause WebSocket connection failures (404 errors)."
250
- );
251
- }
252
- if (url.protocol === "ws:" || url.protocol === "wss:") {
253
- console.warn(
254
- "\u26A0\uFE0F MindCache Cloud: baseUrl uses WebSocket protocol (" + url.protocol + "). Consider using http:// or https:// - CloudAdapter will handle the WebSocket upgrade automatically."
255
- );
256
- }
257
- if (url.hostname === "localhost" && url.port !== "8787" && url.port !== "3000") {
258
- console.warn(
259
- "\u26A0\uFE0F MindCache Cloud: localhost URL detected with non-standard port " + url.port + ". Default MindCache dev server runs on port 8787."
260
- );
261
- }
262
- const wsUrl = baseUrl.replace("https://", "wss://").replace("http://", "ws://");
263
- console.log("\u2601\uFE0F WebSocket will connect to:", wsUrl + "/sync/" + config.instanceId);
264
- } catch (e) {
265
- console.error("\u26A0\uFE0F MindCache Cloud: Invalid baseUrl format:", baseUrl);
266
- }
267
- }
268
232
  /** Browser network status - instantly updated via navigator.onLine */
269
233
  get isOnline() {
270
234
  return this._isOnline;
@@ -517,6 +481,76 @@ var SystemTagHelpers = {
517
481
  hasTemplateInjection: (attrs) => attrs.systemTags.includes("ApplyTemplate")
518
482
  };
519
483
 
484
+ // src/core/SchemaParser.ts
485
+ var SchemaParser = class {
486
+ /**
487
+ * Parse a markdown schema string into a CustomTypeDefinition
488
+ * @param schema - Markdown schema string
489
+ * @returns Parsed type definition
490
+ * @throws Error if schema format is invalid
491
+ */
492
+ static parse(schema) {
493
+ const lines = schema.trim().split("\n").map((line) => line.trim()).filter((line) => line.length > 0);
494
+ if (lines.length === 0) {
495
+ throw new Error("Schema cannot be empty");
496
+ }
497
+ const typeNameMatch = lines[0].match(/^#\s*(\w+)$/);
498
+ if (!typeNameMatch) {
499
+ throw new Error(`Invalid schema: first line must be "#TypeName", got "${lines[0]}"`);
500
+ }
501
+ const typeName = typeNameMatch[1];
502
+ const fields = [];
503
+ for (let i = 1; i < lines.length; i++) {
504
+ const line = lines[i];
505
+ const fieldMatch = line.match(/^\*\s*([^:]+):\s*(.+)$/);
506
+ if (fieldMatch) {
507
+ fields.push({
508
+ name: fieldMatch[1].trim(),
509
+ description: fieldMatch[2].trim()
510
+ });
511
+ }
512
+ }
513
+ if (fields.length === 0) {
514
+ throw new Error(`Schema "${typeName}" must have at least one field`);
515
+ }
516
+ return {
517
+ name: typeName,
518
+ fields,
519
+ rawSchema: schema
520
+ };
521
+ }
522
+ /**
523
+ * Generate a markdown representation of a type definition
524
+ * Useful for including in LLM prompts
525
+ */
526
+ static toMarkdown(typeDef) {
527
+ const lines = [`#${typeDef.name}`];
528
+ for (const field of typeDef.fields) {
529
+ lines.push(`* ${field.name}: ${field.description}`);
530
+ }
531
+ return lines.join("\n");
532
+ }
533
+ /**
534
+ * Generate a prompt-friendly description of the type
535
+ * More verbose than toMarkdown, better for LLM guidance
536
+ */
537
+ static toPromptDescription(typeDef) {
538
+ const fieldDescs = typeDef.fields.map((f) => ` - ${f.name}: ${f.description}`).join("\n");
539
+ return `Type "${typeDef.name}" with fields:
540
+ ${fieldDescs}`;
541
+ }
542
+ /**
543
+ * Generate an example value structure based on the type definition
544
+ */
545
+ static generateExample(typeDef) {
546
+ const lines = [`#${typeDef.name.toLowerCase()}`];
547
+ for (const field of typeDef.fields) {
548
+ lines.push(`* ${field.name}: [${field.description}]`);
549
+ }
550
+ return lines.join("\n");
551
+ }
552
+ };
553
+
520
554
  // src/core/MarkdownSerializer.ts
521
555
  var MarkdownSerializer = class {
522
556
  /**
@@ -863,18 +897,16 @@ var MarkdownSerializer = class {
863
897
  return result;
864
898
  }
865
899
  };
866
-
867
- // src/core/AIToolBuilder.ts
868
900
  var AIToolBuilder = class _AIToolBuilder {
869
901
  /**
870
- * Sanitize key name for use in tool names
871
- */
902
+ * Sanitize key name for use in tool names
903
+ */
872
904
  static sanitizeKeyForTool(key) {
873
905
  return key.replace(/[^a-zA-Z0-9_-]/g, "_");
874
906
  }
875
907
  /**
876
- * Find original key from sanitized tool name
877
- */
908
+ * Find original key from sanitized tool name
909
+ */
878
910
  static findKeyFromSanitizedTool(mc, sanitizedKey) {
879
911
  for (const key of mc.keys()) {
880
912
  if (_AIToolBuilder.sanitizeKeyForTool(key) === sanitizedKey) {
@@ -884,15 +916,174 @@ var AIToolBuilder = class _AIToolBuilder {
884
916
  return void 0;
885
917
  }
886
918
  /**
887
- * Generate Vercel AI SDK compatible tools for writable keys.
888
- * For document type keys, generates additional tools: append_, insert_, edit_
889
- *
890
- * Security: All tools use llm_set_key internally which:
891
- * - Only modifies VALUES, never attributes/systemTags
892
- * - Prevents LLMs from escalating privileges
893
- */
919
+ * Generate framework-agnostic tools with raw JSON Schema.
920
+ * Works with: OpenAI SDK, Anthropic SDK, LangChain, etc.
921
+ *
922
+ * Tool format:
923
+ * {
924
+ * description: string,
925
+ * parameters: { type: 'object', properties: {...}, required: [...] },
926
+ * execute: async (args) => result
927
+ * }
928
+ */
929
+ static createTools(mc) {
930
+ return _AIToolBuilder._buildTools(mc);
931
+ }
932
+ /**
933
+ * Generate Vercel AI SDK v5 compatible tools using Zod schemas.
934
+ * Uses tool() helper with Zod for full AI SDK v5 compatibility.
935
+ *
936
+ * Use this with: generateText(), streamText() from 'ai' package
937
+ */
894
938
  static createVercelAITools(mc) {
895
939
  const tools = {};
940
+ const registeredTypes = mc.getRegisteredTypes();
941
+ const typeDesc = registeredTypes.length > 0 ? `Optional type: ${registeredTypes.join(" | ")}` : "No types registered";
942
+ tools["create_key"] = ai.tool({
943
+ description: `Create a new key in MindCache. ${registeredTypes.length > 0 ? `Available types: ${registeredTypes.join(", ")}` : ""}`,
944
+ inputSchema: zod.z.object({
945
+ key: zod.z.string().describe('The key name (e.g., "contact_john_doe")'),
946
+ value: zod.z.string().describe("The value (JSON string for structured data)"),
947
+ type: zod.z.string().optional().describe(typeDesc)
948
+ }),
949
+ execute: async ({ key, value, type }) => {
950
+ if (mc.has(key)) {
951
+ return { result: `Key "${key}" exists. Use write_${_AIToolBuilder.sanitizeKeyForTool(key)}`, error: true };
952
+ }
953
+ if (type && !mc.getTypeSchema(type)) {
954
+ return { result: `Type "${type}" not registered`, error: true };
955
+ }
956
+ mc.set_value(key, value, { systemTags: ["SystemPrompt", "LLMRead", "LLMWrite"] });
957
+ if (type) {
958
+ mc.setType(key, type);
959
+ }
960
+ return { result: `Created "${key}"${type ? ` (${type})` : ""}`, key, value };
961
+ }
962
+ });
963
+ for (const key of mc.keys()) {
964
+ if (key.startsWith("$") || !mc.keyMatchesContext(key)) {
965
+ continue;
966
+ }
967
+ const attrs = mc.get_attributes(key);
968
+ if (!attrs?.systemTags?.includes("LLMWrite")) {
969
+ continue;
970
+ }
971
+ const sanitized = _AIToolBuilder.sanitizeKeyForTool(key);
972
+ const customTypeName = mc.getKeyType(key);
973
+ const customType = customTypeName ? mc.getTypeSchema(customTypeName) : void 0;
974
+ let desc = `Write to "${key}"`;
975
+ if (customType) {
976
+ desc = `Write to "${key}" (${customTypeName}). ${SchemaParser.toPromptDescription(customType)}`;
977
+ }
978
+ tools[`write_${sanitized}`] = ai.tool({
979
+ description: desc,
980
+ inputSchema: zod.z.object({
981
+ value: zod.z.string().describe(customType ? `JSON following ${customTypeName} schema` : "Value to write")
982
+ }),
983
+ execute: async ({ value }) => {
984
+ const success = mc.llm_set_key(key, value);
985
+ return success ? { result: `Wrote to ${key}`, key, value } : { result: `Failed to write to ${key}`, error: true };
986
+ }
987
+ });
988
+ if (attrs?.type === "document") {
989
+ tools[`append_${sanitized}`] = ai.tool({
990
+ description: `Append to "${key}" document`,
991
+ inputSchema: zod.z.object({ text: zod.z.string().describe("Text to append") }),
992
+ execute: async ({ text }) => {
993
+ const yText = mc.get_document(key);
994
+ if (yText) {
995
+ yText.insert(yText.length, text);
996
+ return { result: "Appended", key };
997
+ }
998
+ return { result: "Not found", error: true };
999
+ }
1000
+ });
1001
+ tools[`insert_${sanitized}`] = ai.tool({
1002
+ description: `Insert text at position in "${key}" document`,
1003
+ inputSchema: zod.z.object({
1004
+ index: zod.z.number().describe("Position (0 = start)"),
1005
+ text: zod.z.string().describe("Text to insert")
1006
+ }),
1007
+ execute: async ({ index, text }) => {
1008
+ mc.insert_text(key, index, text);
1009
+ return { result: `Inserted at ${index}`, key };
1010
+ }
1011
+ });
1012
+ tools[`edit_${sanitized}`] = ai.tool({
1013
+ description: `Find and replace in "${key}" document`,
1014
+ inputSchema: zod.z.object({
1015
+ find: zod.z.string().describe("Text to find"),
1016
+ replace: zod.z.string().describe("Replacement")
1017
+ }),
1018
+ execute: async ({ find, replace }) => {
1019
+ const yText = mc.get_document(key);
1020
+ if (yText) {
1021
+ const text = yText.toString();
1022
+ const idx = text.indexOf(find);
1023
+ if (idx !== -1) {
1024
+ yText.delete(idx, find.length);
1025
+ yText.insert(idx, replace);
1026
+ return { result: `Replaced "${find}"`, key };
1027
+ }
1028
+ return { result: `"${find}" not found`, error: true };
1029
+ }
1030
+ return { result: "Document not found", error: true };
1031
+ }
1032
+ });
1033
+ }
1034
+ }
1035
+ return tools;
1036
+ }
1037
+ /**
1038
+ * Internal: Build tools with raw JSON Schema (framework-agnostic).
1039
+ */
1040
+ static _buildTools(mc) {
1041
+ const tools = {};
1042
+ const registeredTypes = mc.getRegisteredTypes();
1043
+ const typeInfo = registeredTypes.length > 0 ? `Available types: ${registeredTypes.join(", ")}` : "No custom types registered";
1044
+ tools["create_key"] = {
1045
+ description: `Create a new key in MindCache. ${typeInfo}. The new key will be readable and writable by the LLM.`,
1046
+ parameters: {
1047
+ type: "object",
1048
+ properties: {
1049
+ key: { type: "string", description: 'The key name to create (e.g., "contact_john_doe")' },
1050
+ value: { type: "string", description: "The value to store (use JSON string for structured data)" },
1051
+ type: {
1052
+ type: "string",
1053
+ description: registeredTypes.length > 0 ? `Optional: custom type name (${registeredTypes.join(" | ")})` : "Optional: custom type name (none registered)"
1054
+ }
1055
+ },
1056
+ required: ["key", "value"]
1057
+ },
1058
+ execute: async ({ key, value, type }) => {
1059
+ if (mc.has(key)) {
1060
+ return {
1061
+ result: `Key "${key}" already exists. Use write_${_AIToolBuilder.sanitizeKeyForTool(key)} to update it.`,
1062
+ key,
1063
+ error: true
1064
+ };
1065
+ }
1066
+ if (type && !mc.getTypeSchema(type)) {
1067
+ return {
1068
+ result: `Type "${type}" is not registered. Available types: ${registeredTypes.join(", ") || "none"}`,
1069
+ key,
1070
+ error: true
1071
+ };
1072
+ }
1073
+ mc.set_value(key, value, {
1074
+ systemTags: ["SystemPrompt", "LLMRead", "LLMWrite"]
1075
+ });
1076
+ if (type) {
1077
+ mc.setType(key, type);
1078
+ }
1079
+ return {
1080
+ result: `Successfully created key "${key}"${type ? ` with type "${type}"` : ""}`,
1081
+ key,
1082
+ value,
1083
+ type
1084
+ };
1085
+ }
1086
+ };
896
1087
  for (const key of mc.keys()) {
897
1088
  if (key.startsWith("$")) {
898
1089
  continue;
@@ -907,12 +1098,28 @@ var AIToolBuilder = class _AIToolBuilder {
907
1098
  }
908
1099
  const sanitizedKey = _AIToolBuilder.sanitizeKeyForTool(key);
909
1100
  const isDocument = attributes?.type === "document";
1101
+ const customTypeName = mc.getKeyType(key);
1102
+ const customType = customTypeName ? mc.getTypeSchema(customTypeName) : void 0;
1103
+ let writeDescription;
1104
+ if (customType) {
1105
+ const schemaGuidance = SchemaParser.toPromptDescription(customType);
1106
+ const example = SchemaParser.generateExample(customType);
1107
+ writeDescription = `Write a value to "${key}" that must follow this schema:
1108
+ ${schemaGuidance}
1109
+
1110
+ Example format:
1111
+ ${example}`;
1112
+ } else if (isDocument) {
1113
+ writeDescription = `Rewrite the entire "${key}" document`;
1114
+ } else {
1115
+ writeDescription = `Write a value to the STM key: ${key}`;
1116
+ }
910
1117
  tools[`write_${sanitizedKey}`] = {
911
- description: isDocument ? `Rewrite the entire "${key}" document` : `Write a value to the STM key: ${key}`,
912
- inputSchema: {
1118
+ description: writeDescription,
1119
+ parameters: {
913
1120
  type: "object",
914
1121
  properties: {
915
- value: { type: "string", description: isDocument ? "New document content" : "The value to write" }
1122
+ value: { type: "string", description: customType ? `Value following ${customTypeName} schema` : isDocument ? "New document content" : "The value to write" }
916
1123
  },
917
1124
  required: ["value"]
918
1125
  },
@@ -935,7 +1142,7 @@ var AIToolBuilder = class _AIToolBuilder {
935
1142
  if (isDocument) {
936
1143
  tools[`append_${sanitizedKey}`] = {
937
1144
  description: `Append text to the end of "${key}" document`,
938
- inputSchema: {
1145
+ parameters: {
939
1146
  type: "object",
940
1147
  properties: {
941
1148
  text: { type: "string", description: "Text to append" }
@@ -960,7 +1167,7 @@ var AIToolBuilder = class _AIToolBuilder {
960
1167
  };
961
1168
  tools[`insert_${sanitizedKey}`] = {
962
1169
  description: `Insert text at a position in "${key}" document`,
963
- inputSchema: {
1170
+ parameters: {
964
1171
  type: "object",
965
1172
  properties: {
966
1173
  index: { type: "number", description: "Position to insert at (0 = start)" },
@@ -983,7 +1190,7 @@ var AIToolBuilder = class _AIToolBuilder {
983
1190
  };
984
1191
  tools[`edit_${sanitizedKey}`] = {
985
1192
  description: `Find and replace text in "${key}" document`,
986
- inputSchema: {
1193
+ parameters: {
987
1194
  type: "object",
988
1195
  properties: {
989
1196
  find: { type: "string", description: "Text to find" },
@@ -1020,11 +1227,16 @@ var AIToolBuilder = class _AIToolBuilder {
1020
1227
  return tools;
1021
1228
  }
1022
1229
  /**
1023
- * Generate a system prompt containing all visible STM keys and their values.
1024
- * Indicates which tools can be used to modify writable keys.
1025
- */
1230
+ * Generate a system prompt containing all visible STM keys and their values.
1231
+ * Indicates which tools can be used to modify writable keys.
1232
+ */
1026
1233
  static getSystemPrompt(mc) {
1027
1234
  const lines = [];
1235
+ const registeredTypes = mc.getRegisteredTypes();
1236
+ if (registeredTypes.length > 0) {
1237
+ lines.push(`[create_key tool available - registered types: ${registeredTypes.join(", ")}]`);
1238
+ lines.push("");
1239
+ }
1028
1240
  for (const key of mc.keys()) {
1029
1241
  if (key.startsWith("$")) {
1030
1242
  continue;
@@ -1042,8 +1254,18 @@ var AIToolBuilder = class _AIToolBuilder {
1042
1254
  const isWritable = attributes?.systemTags?.includes("LLMWrite");
1043
1255
  const isDocument = attributes?.type === "document";
1044
1256
  const sanitizedKey = _AIToolBuilder.sanitizeKeyForTool(key);
1257
+ const customTypeName = mc.getKeyType(key);
1258
+ const customType = customTypeName ? mc.getTypeSchema(customTypeName) : void 0;
1045
1259
  if (isWritable) {
1046
- if (isDocument) {
1260
+ if (customType) {
1261
+ const schemaInfo = SchemaParser.toMarkdown(customType);
1262
+ lines.push(
1263
+ `${key} (type: ${customTypeName}): ${displayValue}
1264
+ Schema:
1265
+ ${schemaInfo}
1266
+ Tool: write_${sanitizedKey}`
1267
+ );
1268
+ } else if (isDocument) {
1047
1269
  lines.push(
1048
1270
  `${key}: ${displayValue}. Document tools: write_${sanitizedKey}, append_${sanitizedKey}, edit_${sanitizedKey}`
1049
1271
  );
@@ -1054,15 +1276,19 @@ var AIToolBuilder = class _AIToolBuilder {
1054
1276
  );
1055
1277
  }
1056
1278
  } else {
1057
- lines.push(`${key}: ${displayValue}`);
1279
+ if (customTypeName) {
1280
+ lines.push(`${key} (type: ${customTypeName}): ${displayValue}`);
1281
+ } else {
1282
+ lines.push(`${key}: ${displayValue}`);
1283
+ }
1058
1284
  }
1059
1285
  }
1060
1286
  return lines.join("\n");
1061
1287
  }
1062
1288
  /**
1063
- * Execute a tool call by name with the given value.
1064
- * Returns the result or null if tool not found.
1065
- */
1289
+ * Execute a tool call by name with the given value.
1290
+ * Returns the result or null if tool not found.
1291
+ */
1066
1292
  static executeToolCall(mc, toolName, value) {
1067
1293
  const match = toolName.match(/^(write|append|insert|edit)_(.+)$/);
1068
1294
  if (!match) {
@@ -1389,7 +1615,7 @@ var MindCache = class {
1389
1615
  listeners = {};
1390
1616
  globalListeners = [];
1391
1617
  // Metadata
1392
- version = "3.3.2";
1618
+ version = "3.6.0";
1393
1619
  // Internal flag to prevent sync loops when receiving remote updates
1394
1620
  // (Less critical with Yjs but kept for API compat)
1395
1621
  normalizeSystemTags(tags) {
@@ -1426,6 +1652,8 @@ var MindCache = class {
1426
1652
  _history = [];
1427
1653
  _historyOptions = { maxEntries: 100, snapshotInterval: 10 };
1428
1654
  _historyEnabled = false;
1655
+ // Custom type registry
1656
+ _typeRegistry = /* @__PURE__ */ new Map();
1429
1657
  constructor(options) {
1430
1658
  this.doc = options?.doc || new Y__namespace.Doc();
1431
1659
  this.rootMap = this.doc.getMap("mindcache");
@@ -2382,6 +2610,71 @@ var MindCache = class {
2382
2610
  systemGetKeysByTag(tag) {
2383
2611
  return TagManager.systemGetKeysByTag(this, tag);
2384
2612
  }
2613
+ // ============================================
2614
+ // Custom Type Methods
2615
+ // ============================================
2616
+ /**
2617
+ * Register a custom type with a markdown schema definition.
2618
+ *
2619
+ * Schema format:
2620
+ * ```
2621
+ * #TypeName
2622
+ * * fieldName: description of the field
2623
+ * * anotherField: description
2624
+ * ```
2625
+ *
2626
+ * @param name - Type name (e.g., 'Contact')
2627
+ * @param schema - Markdown schema definition
2628
+ * @throws Error if schema format is invalid
2629
+ */
2630
+ registerType(name, schema) {
2631
+ const typeDef = SchemaParser.parse(schema);
2632
+ typeDef.name = name;
2633
+ this._typeRegistry.set(name, typeDef);
2634
+ }
2635
+ /**
2636
+ * Assign a custom type to a key.
2637
+ * The key must exist and the type must be registered.
2638
+ * Also sets the underlying type to 'json' since custom types are structured JSON data.
2639
+ *
2640
+ * @param key - Key to assign type to
2641
+ * @param typeName - Registered type name
2642
+ * @throws Error if key doesn't exist or type is not registered
2643
+ */
2644
+ setType(key, typeName) {
2645
+ if (!this.rootMap.has(key)) {
2646
+ throw new Error(`Key "${key}" does not exist`);
2647
+ }
2648
+ if (!this._typeRegistry.has(typeName)) {
2649
+ throw new Error(`Type "${typeName}" is not registered. Use registerType() first.`);
2650
+ }
2651
+ this.set_attributes(key, { type: "json", customType: typeName });
2652
+ }
2653
+ /**
2654
+ * Get a registered type schema definition.
2655
+ *
2656
+ * @param typeName - Type name to look up
2657
+ * @returns The type definition or undefined if not registered
2658
+ */
2659
+ getTypeSchema(typeName) {
2660
+ return this._typeRegistry.get(typeName);
2661
+ }
2662
+ /**
2663
+ * Get all registered type names.
2664
+ */
2665
+ getRegisteredTypes() {
2666
+ return Array.from(this._typeRegistry.keys());
2667
+ }
2668
+ /**
2669
+ * Get the custom type assigned to a key.
2670
+ *
2671
+ * @param key - Key to check
2672
+ * @returns Type name or undefined if no custom type assigned
2673
+ */
2674
+ getKeyType(key) {
2675
+ const attrs = this.get_attributes(key);
2676
+ return attrs?.customType;
2677
+ }
2385
2678
  /**
2386
2679
  * Helper to get sorted keys (by zIndex).
2387
2680
  * Respects context filtering when set.
@@ -2643,9 +2936,28 @@ var MindCache = class {
2643
2936
  findKeyFromSanitizedTool(sanitizedKey) {
2644
2937
  return AIToolBuilder.findKeyFromSanitizedTool(this, sanitizedKey);
2645
2938
  }
2939
+ /**
2940
+ * Generate framework-agnostic tools with raw JSON Schema.
2941
+ * Works with: OpenAI SDK, Anthropic SDK, LangChain, and other frameworks.
2942
+ *
2943
+ * Tool format:
2944
+ * {
2945
+ * description: string,
2946
+ * parameters: { type: 'object', properties: {...}, required: [...] },
2947
+ * execute: async (args) => result
2948
+ * }
2949
+ *
2950
+ * Security: All tools use llm_set_key internally which:
2951
+ * - Only modifies VALUES, never attributes/systemTags
2952
+ * - Prevents LLMs from escalating privileges
2953
+ */
2954
+ create_tools() {
2955
+ return AIToolBuilder.createTools(this);
2956
+ }
2646
2957
  /**
2647
2958
  * Generate Vercel AI SDK compatible tools for writable keys.
2648
- * For document type keys, generates additional tools: append_, insert_, edit_
2959
+ * Wraps parameters with jsonSchema() for AI SDK v5 compatibility.
2960
+ * Use this with: generateText(), streamText() from 'ai' package.
2649
2961
  *
2650
2962
  * Security: All tools use llm_set_key internally which:
2651
2963
  * - Only modifies VALUES, never attributes/systemTags
@@ -2688,9 +3000,6 @@ init_CloudAdapter();
2688
3000
  init_CloudAdapter();
2689
3001
 
2690
3002
  // src/cloud/OAuthClient.ts
2691
- var DEFAULT_AUTH_URL = "https://api.mindcache.dev/oauth/authorize";
2692
- var DEFAULT_TOKEN_URL = "https://api.mindcache.dev/oauth/token";
2693
- var DEFAULT_USERINFO_URL = "https://api.mindcache.dev/oauth/userinfo";
2694
3003
  var TOKEN_REFRESH_BUFFER = 5 * 60 * 1e3;
2695
3004
  function generateRandomString(length2) {
2696
3005
  const array = new Uint8Array(length2);
@@ -2719,6 +3028,21 @@ var OAuthClient = class {
2719
3028
  tokens = null;
2720
3029
  refreshPromise = null;
2721
3030
  constructor(config) {
3031
+ if (!config.baseUrl) {
3032
+ throw new Error(
3033
+ 'MindCache OAuth: baseUrl is required!\n For production: baseUrl: "https://api.mindcache.dev"\n For local dev: baseUrl: "http://localhost:8787"'
3034
+ );
3035
+ }
3036
+ try {
3037
+ const url = new URL(config.baseUrl);
3038
+ if (url.hostname === "mindcache.dev") {
3039
+ console.error(
3040
+ '\u274C MindCache OAuth ERROR: baseUrl should be "api.mindcache.dev" not "mindcache.dev"\n Current: ' + config.baseUrl + "\n Correct: https://api.mindcache.dev"
3041
+ );
3042
+ }
3043
+ } catch {
3044
+ throw new Error("MindCache OAuth: Invalid baseUrl format: " + config.baseUrl);
3045
+ }
2722
3046
  let redirectUri = config.redirectUri;
2723
3047
  if (!redirectUri && typeof window !== "undefined") {
2724
3048
  const url = new URL(window.location.href);
@@ -2726,19 +3050,57 @@ var OAuthClient = class {
2726
3050
  url.hash = "";
2727
3051
  redirectUri = url.toString();
2728
3052
  }
3053
+ const baseUrl = config.baseUrl.replace(/\/$/, "");
2729
3054
  this.config = {
2730
3055
  clientId: config.clientId,
3056
+ baseUrl,
2731
3057
  redirectUri: redirectUri || "",
2732
3058
  scopes: config.scopes || ["read", "write"],
2733
- authUrl: config.authUrl || DEFAULT_AUTH_URL,
2734
- tokenUrl: config.tokenUrl || DEFAULT_TOKEN_URL,
2735
- apiUrl: config.apiUrl || (config.tokenUrl || DEFAULT_TOKEN_URL).replace("/oauth/token", ""),
2736
3059
  usePKCE: config.usePKCE !== false,
2737
3060
  // Default true
2738
3061
  storagePrefix: config.storagePrefix || "mindcache_oauth"
2739
3062
  };
3063
+ console.log("\u{1F510} MindCache OAuth:", {
3064
+ baseUrl: this.config.baseUrl,
3065
+ authUrl: this.authUrl,
3066
+ tokenUrl: this.tokenUrl,
3067
+ clientId: this.config.clientId.substring(0, 20) + "..."
3068
+ });
3069
+ this.validateApi();
2740
3070
  this.loadTokens();
2741
3071
  }
3072
+ /** Derived auth URL */
3073
+ get authUrl() {
3074
+ return this.config.baseUrl + "/oauth/authorize";
3075
+ }
3076
+ /** Derived token URL */
3077
+ get tokenUrl() {
3078
+ return this.config.baseUrl + "/oauth/token";
3079
+ }
3080
+ /** Derived userinfo URL */
3081
+ get userinfoUrl() {
3082
+ return this.config.baseUrl + "/oauth/userinfo";
3083
+ }
3084
+ /**
3085
+ * Validate the API is reachable
3086
+ */
3087
+ async validateApi() {
3088
+ try {
3089
+ const response = await fetch(`${this.config.baseUrl}/oauth/apps/info`, {
3090
+ method: "GET",
3091
+ headers: { "Accept": "application/json" }
3092
+ });
3093
+ if (response.status === 404) {
3094
+ console.error(
3095
+ "\u274C MindCache OAuth ERROR: API not found at " + this.config.baseUrl + '\n The server returned 404. Common causes:\n - Wrong domain: Use "api.mindcache.dev" not "mindcache.dev"\n - Wrong port: Local dev server is usually on port 8787\n - Server not running: Make sure the MindCache server is started'
3096
+ );
3097
+ }
3098
+ } catch (error) {
3099
+ console.error(
3100
+ "\u274C MindCache OAuth ERROR: Cannot reach API at " + this.config.baseUrl + "\n Error: " + (error instanceof Error ? error.message : String(error))
3101
+ );
3102
+ }
3103
+ }
2742
3104
  /**
2743
3105
  * Check if user is authenticated
2744
3106
  */
@@ -2764,7 +3126,7 @@ var OAuthClient = class {
2764
3126
  async authorize(options) {
2765
3127
  const state = options?.state || generateRandomString(32);
2766
3128
  this.setStorage("state", state);
2767
- const url = new URL(this.config.authUrl);
3129
+ const url = new URL(this.authUrl);
2768
3130
  url.searchParams.set("response_type", "code");
2769
3131
  url.searchParams.set("client_id", this.config.clientId);
2770
3132
  url.searchParams.set("redirect_uri", this.config.redirectUri);
@@ -2827,7 +3189,7 @@ var OAuthClient = class {
2827
3189
  }
2828
3190
  body.code_verifier = codeVerifier;
2829
3191
  }
2830
- const response = await fetch(this.config.tokenUrl, {
3192
+ const response = await fetch(this.tokenUrl, {
2831
3193
  method: "POST",
2832
3194
  headers: {
2833
3195
  "Content-Type": "application/json"
@@ -2879,7 +3241,7 @@ var OAuthClient = class {
2879
3241
  throw new Error("No refresh token available");
2880
3242
  }
2881
3243
  try {
2882
- const response = await fetch(this.config.tokenUrl, {
3244
+ const response = await fetch(this.tokenUrl, {
2883
3245
  method: "POST",
2884
3246
  headers: {
2885
3247
  "Content-Type": "application/json"
@@ -2913,7 +3275,7 @@ var OAuthClient = class {
2913
3275
  */
2914
3276
  async getUserInfo() {
2915
3277
  const token = await this.getAccessToken();
2916
- const response = await fetch(DEFAULT_USERINFO_URL, {
3278
+ const response = await fetch(this.userinfoUrl, {
2917
3279
  headers: {
2918
3280
  Authorization: `Bearer ${token}`
2919
3281
  }
@@ -2935,7 +3297,7 @@ var OAuthClient = class {
2935
3297
  async logout() {
2936
3298
  if (this.tokens?.accessToken) {
2937
3299
  try {
2938
- await fetch(this.config.tokenUrl.replace("/token", "/revoke"), {
3300
+ await fetch(this.tokenUrl.replace("/token", "/revoke"), {
2939
3301
  method: "POST",
2940
3302
  headers: {
2941
3303
  "Content-Type": "application/json"
@@ -2958,7 +3320,7 @@ var OAuthClient = class {
2958
3320
  }
2959
3321
  /**
2960
3322
  * Token provider for MindCache cloud config
2961
- * This fetches a WebSocket token (short-lived) using the OAuth access token
3323
+ * This fetches a WebSocket token using the OAuth access token
2962
3324
  * Use this with MindCacheCloudOptions.tokenProvider
2963
3325
  */
2964
3326
  tokenProvider = async () => {
@@ -2967,7 +3329,7 @@ var OAuthClient = class {
2967
3329
  if (!instanceId) {
2968
3330
  throw new Error("No instance ID available. Complete OAuth flow first.");
2969
3331
  }
2970
- const response = await fetch(`${this.config.apiUrl}/api/ws-token`, {
3332
+ const response = await fetch(`${this.config.baseUrl}/api/ws-token`, {
2971
3333
  method: "POST",
2972
3334
  headers: {
2973
3335
  "Content-Type": "application/json",
@@ -2987,7 +3349,6 @@ var OAuthClient = class {
2987
3349
  };
2988
3350
  /**
2989
3351
  * Get raw OAuth access token (for API calls, not WebSocket)
2990
- * Use getAccessToken() for most cases
2991
3352
  */
2992
3353
  accessTokenProvider = async () => {
2993
3354
  return this.getAccessToken();
@@ -3079,6 +3440,7 @@ var mindcache = new MindCache();
3079
3440
  exports.DEFAULT_KEY_ATTRIBUTES = DEFAULT_KEY_ATTRIBUTES;
3080
3441
  exports.MindCache = MindCache;
3081
3442
  exports.OAuthClient = OAuthClient;
3443
+ exports.SchemaParser = SchemaParser;
3082
3444
  exports.SystemTagHelpers = SystemTagHelpers;
3083
3445
  exports.createOAuthClient = createOAuthClient;
3084
3446
  exports.mindcache = mindcache;