mindcache 3.5.3 → 3.7.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.mjs CHANGED
@@ -4,7 +4,11 @@ import * as decoding from 'lib0/decoding';
4
4
  import * as Y from 'yjs';
5
5
  import { IndexeddbPersistence } from 'y-indexeddb';
6
6
  import diff from 'fast-diff';
7
- import { useState, useRef, useEffect } from 'react';
7
+ import { tool, streamText, stepCountIs } from 'ai';
8
+ import { z } from 'zod';
9
+ import React2, { createContext, useState, useRef, useEffect, useContext, useCallback } from 'react';
10
+ import { createOpenAI } from '@ai-sdk/openai';
11
+ import { jsx, jsxs } from 'react/jsx-runtime';
8
12
 
9
13
  var __defProp = Object.defineProperty;
10
14
  var __getOwnPropNames = Object.getOwnPropertyNames;
@@ -184,7 +188,6 @@ var init_CloudAdapter = __esm({
184
188
  if (!config.baseUrl) {
185
189
  throw new Error("MindCache Cloud: baseUrl is required. Please provide the cloud API URL in your configuration.");
186
190
  }
187
- this.validateConfig(config);
188
191
  this.setupNetworkDetection();
189
192
  }
190
193
  ws = null;
@@ -200,43 +203,6 @@ var init_CloudAdapter = __esm({
200
203
  handleOnline = null;
201
204
  handleOffline = null;
202
205
  _synced = false;
203
- /**
204
- * Validate configuration and warn about common mistakes
205
- */
206
- validateConfig(config) {
207
- const baseUrl = config.baseUrl;
208
- if (!baseUrl) {
209
- return;
210
- }
211
- console.log("\u2601\uFE0F MindCache Cloud Config:", {
212
- baseUrl,
213
- instanceId: config.instanceId,
214
- hasTokenProvider: !!config.tokenProvider,
215
- hasApiKey: !!config.apiKey
216
- });
217
- try {
218
- const url = new URL(baseUrl);
219
- if (url.hostname === "mindcache.dev") {
220
- console.error(
221
- '\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)."
222
- );
223
- }
224
- if (url.protocol === "ws:" || url.protocol === "wss:") {
225
- console.warn(
226
- "\u26A0\uFE0F MindCache Cloud: baseUrl uses WebSocket protocol (" + url.protocol + "). Consider using http:// or https:// - CloudAdapter will handle the WebSocket upgrade automatically."
227
- );
228
- }
229
- if (url.hostname === "localhost" && url.port !== "8787" && url.port !== "3000") {
230
- console.warn(
231
- "\u26A0\uFE0F MindCache Cloud: localhost URL detected with non-standard port " + url.port + ". Default MindCache dev server runs on port 8787."
232
- );
233
- }
234
- const wsUrl = baseUrl.replace("https://", "wss://").replace("http://", "ws://");
235
- console.log("\u2601\uFE0F WebSocket will connect to:", wsUrl + "/sync/" + config.instanceId);
236
- } catch (e) {
237
- console.error("\u26A0\uFE0F MindCache Cloud: Invalid baseUrl format:", baseUrl);
238
- }
239
- }
240
206
  /** Browser network status - instantly updated via navigator.onLine */
241
207
  get isOnline() {
242
208
  return this._isOnline;
@@ -489,6 +455,76 @@ var SystemTagHelpers = {
489
455
  hasTemplateInjection: (attrs) => attrs.systemTags.includes("ApplyTemplate")
490
456
  };
491
457
 
458
+ // src/core/SchemaParser.ts
459
+ var SchemaParser = class {
460
+ /**
461
+ * Parse a markdown schema string into a CustomTypeDefinition
462
+ * @param schema - Markdown schema string
463
+ * @returns Parsed type definition
464
+ * @throws Error if schema format is invalid
465
+ */
466
+ static parse(schema) {
467
+ const lines = schema.trim().split("\n").map((line) => line.trim()).filter((line) => line.length > 0);
468
+ if (lines.length === 0) {
469
+ throw new Error("Schema cannot be empty");
470
+ }
471
+ const typeNameMatch = lines[0].match(/^#\s*(\w+)$/);
472
+ if (!typeNameMatch) {
473
+ throw new Error(`Invalid schema: first line must be "#TypeName", got "${lines[0]}"`);
474
+ }
475
+ const typeName = typeNameMatch[1];
476
+ const fields = [];
477
+ for (let i = 1; i < lines.length; i++) {
478
+ const line = lines[i];
479
+ const fieldMatch = line.match(/^\*\s*([^:]+):\s*(.+)$/);
480
+ if (fieldMatch) {
481
+ fields.push({
482
+ name: fieldMatch[1].trim(),
483
+ description: fieldMatch[2].trim()
484
+ });
485
+ }
486
+ }
487
+ if (fields.length === 0) {
488
+ throw new Error(`Schema "${typeName}" must have at least one field`);
489
+ }
490
+ return {
491
+ name: typeName,
492
+ fields,
493
+ rawSchema: schema
494
+ };
495
+ }
496
+ /**
497
+ * Generate a markdown representation of a type definition
498
+ * Useful for including in LLM prompts
499
+ */
500
+ static toMarkdown(typeDef) {
501
+ const lines = [`#${typeDef.name}`];
502
+ for (const field of typeDef.fields) {
503
+ lines.push(`* ${field.name}: ${field.description}`);
504
+ }
505
+ return lines.join("\n");
506
+ }
507
+ /**
508
+ * Generate a prompt-friendly description of the type
509
+ * More verbose than toMarkdown, better for LLM guidance
510
+ */
511
+ static toPromptDescription(typeDef) {
512
+ const fieldDescs = typeDef.fields.map((f) => ` - ${f.name}: ${f.description}`).join("\n");
513
+ return `Type "${typeDef.name}" with fields:
514
+ ${fieldDescs}`;
515
+ }
516
+ /**
517
+ * Generate an example value structure based on the type definition
518
+ */
519
+ static generateExample(typeDef) {
520
+ const lines = [`#${typeDef.name.toLowerCase()}`];
521
+ for (const field of typeDef.fields) {
522
+ lines.push(`* ${field.name}: [${field.description}]`);
523
+ }
524
+ return lines.join("\n");
525
+ }
526
+ };
527
+
492
528
  // src/core/MarkdownSerializer.ts
493
529
  var MarkdownSerializer = class {
494
530
  /**
@@ -835,18 +871,16 @@ var MarkdownSerializer = class {
835
871
  return result;
836
872
  }
837
873
  };
838
-
839
- // src/core/AIToolBuilder.ts
840
874
  var AIToolBuilder = class _AIToolBuilder {
841
875
  /**
842
- * Sanitize key name for use in tool names
843
- */
876
+ * Sanitize key name for use in tool names
877
+ */
844
878
  static sanitizeKeyForTool(key) {
845
879
  return key.replace(/[^a-zA-Z0-9_-]/g, "_");
846
880
  }
847
881
  /**
848
- * Find original key from sanitized tool name
849
- */
882
+ * Find original key from sanitized tool name
883
+ */
850
884
  static findKeyFromSanitizedTool(mc, sanitizedKey) {
851
885
  for (const key of mc.keys()) {
852
886
  if (_AIToolBuilder.sanitizeKeyForTool(key) === sanitizedKey) {
@@ -856,15 +890,174 @@ var AIToolBuilder = class _AIToolBuilder {
856
890
  return void 0;
857
891
  }
858
892
  /**
859
- * Generate Vercel AI SDK compatible tools for writable keys.
860
- * For document type keys, generates additional tools: append_, insert_, edit_
861
- *
862
- * Security: All tools use llm_set_key internally which:
863
- * - Only modifies VALUES, never attributes/systemTags
864
- * - Prevents LLMs from escalating privileges
865
- */
893
+ * Generate framework-agnostic tools with raw JSON Schema.
894
+ * Works with: OpenAI SDK, Anthropic SDK, LangChain, etc.
895
+ *
896
+ * Tool format:
897
+ * {
898
+ * description: string,
899
+ * parameters: { type: 'object', properties: {...}, required: [...] },
900
+ * execute: async (args) => result
901
+ * }
902
+ */
903
+ static createTools(mc) {
904
+ return _AIToolBuilder._buildTools(mc);
905
+ }
906
+ /**
907
+ * Generate Vercel AI SDK v5 compatible tools using Zod schemas.
908
+ * Uses tool() helper with Zod for full AI SDK v5 compatibility.
909
+ *
910
+ * Use this with: generateText(), streamText() from 'ai' package
911
+ */
866
912
  static createVercelAITools(mc) {
867
913
  const tools = {};
914
+ const registeredTypes = mc.getRegisteredTypes();
915
+ const typeDesc = registeredTypes.length > 0 ? `Optional type: ${registeredTypes.join(" | ")}` : "No types registered";
916
+ tools["create_key"] = tool({
917
+ description: `Create a new key in MindCache. ${registeredTypes.length > 0 ? `Available types: ${registeredTypes.join(", ")}` : ""}`,
918
+ inputSchema: z.object({
919
+ key: z.string().describe('The key name (e.g., "contact_john_doe")'),
920
+ value: z.string().describe("The value (JSON string for structured data)"),
921
+ type: z.string().optional().describe(typeDesc)
922
+ }),
923
+ execute: async ({ key, value, type }) => {
924
+ if (mc.has(key)) {
925
+ return { result: `Key "${key}" exists. Use write_${_AIToolBuilder.sanitizeKeyForTool(key)}`, error: true };
926
+ }
927
+ if (type && !mc.getTypeSchema(type)) {
928
+ return { result: `Type "${type}" not registered`, error: true };
929
+ }
930
+ mc.set_value(key, value, { systemTags: ["SystemPrompt", "LLMRead", "LLMWrite"] });
931
+ if (type) {
932
+ mc.setType(key, type);
933
+ }
934
+ return { result: `Created "${key}"${type ? ` (${type})` : ""}`, key, value };
935
+ }
936
+ });
937
+ for (const key of mc.keys()) {
938
+ if (key.startsWith("$") || !mc.keyMatchesContext(key)) {
939
+ continue;
940
+ }
941
+ const attrs = mc.get_attributes(key);
942
+ if (!attrs?.systemTags?.includes("LLMWrite")) {
943
+ continue;
944
+ }
945
+ const sanitized = _AIToolBuilder.sanitizeKeyForTool(key);
946
+ const customTypeName = mc.getKeyType(key);
947
+ const customType = customTypeName ? mc.getTypeSchema(customTypeName) : void 0;
948
+ let desc = `Write to "${key}"`;
949
+ if (customType) {
950
+ desc = `Write to "${key}" (${customTypeName}). ${SchemaParser.toPromptDescription(customType)}`;
951
+ }
952
+ tools[`write_${sanitized}`] = tool({
953
+ description: desc,
954
+ inputSchema: z.object({
955
+ value: z.string().describe(customType ? `JSON following ${customTypeName} schema` : "Value to write")
956
+ }),
957
+ execute: async ({ value }) => {
958
+ const success = mc.llm_set_key(key, value);
959
+ return success ? { result: `Wrote to ${key}`, key, value } : { result: `Failed to write to ${key}`, error: true };
960
+ }
961
+ });
962
+ if (attrs?.type === "document") {
963
+ tools[`append_${sanitized}`] = tool({
964
+ description: `Append to "${key}" document`,
965
+ inputSchema: z.object({ text: z.string().describe("Text to append") }),
966
+ execute: async ({ text }) => {
967
+ const yText = mc.get_document(key);
968
+ if (yText) {
969
+ yText.insert(yText.length, text);
970
+ return { result: "Appended", key };
971
+ }
972
+ return { result: "Not found", error: true };
973
+ }
974
+ });
975
+ tools[`insert_${sanitized}`] = tool({
976
+ description: `Insert text at position in "${key}" document`,
977
+ inputSchema: z.object({
978
+ index: z.number().describe("Position (0 = start)"),
979
+ text: z.string().describe("Text to insert")
980
+ }),
981
+ execute: async ({ index, text }) => {
982
+ mc.insert_text(key, index, text);
983
+ return { result: `Inserted at ${index}`, key };
984
+ }
985
+ });
986
+ tools[`edit_${sanitized}`] = tool({
987
+ description: `Find and replace in "${key}" document`,
988
+ inputSchema: z.object({
989
+ find: z.string().describe("Text to find"),
990
+ replace: z.string().describe("Replacement")
991
+ }),
992
+ execute: async ({ find, replace }) => {
993
+ const yText = mc.get_document(key);
994
+ if (yText) {
995
+ const text = yText.toString();
996
+ const idx = text.indexOf(find);
997
+ if (idx !== -1) {
998
+ yText.delete(idx, find.length);
999
+ yText.insert(idx, replace);
1000
+ return { result: `Replaced "${find}"`, key };
1001
+ }
1002
+ return { result: `"${find}" not found`, error: true };
1003
+ }
1004
+ return { result: "Document not found", error: true };
1005
+ }
1006
+ });
1007
+ }
1008
+ }
1009
+ return tools;
1010
+ }
1011
+ /**
1012
+ * Internal: Build tools with raw JSON Schema (framework-agnostic).
1013
+ */
1014
+ static _buildTools(mc) {
1015
+ const tools = {};
1016
+ const registeredTypes = mc.getRegisteredTypes();
1017
+ const typeInfo = registeredTypes.length > 0 ? `Available types: ${registeredTypes.join(", ")}` : "No custom types registered";
1018
+ tools["create_key"] = {
1019
+ description: `Create a new key in MindCache. ${typeInfo}. The new key will be readable and writable by the LLM.`,
1020
+ parameters: {
1021
+ type: "object",
1022
+ properties: {
1023
+ key: { type: "string", description: 'The key name to create (e.g., "contact_john_doe")' },
1024
+ value: { type: "string", description: "The value to store (use JSON string for structured data)" },
1025
+ type: {
1026
+ type: "string",
1027
+ description: registeredTypes.length > 0 ? `Optional: custom type name (${registeredTypes.join(" | ")})` : "Optional: custom type name (none registered)"
1028
+ }
1029
+ },
1030
+ required: ["key", "value"]
1031
+ },
1032
+ execute: async ({ key, value, type }) => {
1033
+ if (mc.has(key)) {
1034
+ return {
1035
+ result: `Key "${key}" already exists. Use write_${_AIToolBuilder.sanitizeKeyForTool(key)} to update it.`,
1036
+ key,
1037
+ error: true
1038
+ };
1039
+ }
1040
+ if (type && !mc.getTypeSchema(type)) {
1041
+ return {
1042
+ result: `Type "${type}" is not registered. Available types: ${registeredTypes.join(", ") || "none"}`,
1043
+ key,
1044
+ error: true
1045
+ };
1046
+ }
1047
+ mc.set_value(key, value, {
1048
+ systemTags: ["SystemPrompt", "LLMRead", "LLMWrite"]
1049
+ });
1050
+ if (type) {
1051
+ mc.setType(key, type);
1052
+ }
1053
+ return {
1054
+ result: `Successfully created key "${key}"${type ? ` with type "${type}"` : ""}`,
1055
+ key,
1056
+ value,
1057
+ type
1058
+ };
1059
+ }
1060
+ };
868
1061
  for (const key of mc.keys()) {
869
1062
  if (key.startsWith("$")) {
870
1063
  continue;
@@ -879,12 +1072,28 @@ var AIToolBuilder = class _AIToolBuilder {
879
1072
  }
880
1073
  const sanitizedKey = _AIToolBuilder.sanitizeKeyForTool(key);
881
1074
  const isDocument = attributes?.type === "document";
1075
+ const customTypeName = mc.getKeyType(key);
1076
+ const customType = customTypeName ? mc.getTypeSchema(customTypeName) : void 0;
1077
+ let writeDescription;
1078
+ if (customType) {
1079
+ const schemaGuidance = SchemaParser.toPromptDescription(customType);
1080
+ const example = SchemaParser.generateExample(customType);
1081
+ writeDescription = `Write a value to "${key}" that must follow this schema:
1082
+ ${schemaGuidance}
1083
+
1084
+ Example format:
1085
+ ${example}`;
1086
+ } else if (isDocument) {
1087
+ writeDescription = `Rewrite the entire "${key}" document`;
1088
+ } else {
1089
+ writeDescription = `Write a value to the STM key: ${key}`;
1090
+ }
882
1091
  tools[`write_${sanitizedKey}`] = {
883
- description: isDocument ? `Rewrite the entire "${key}" document` : `Write a value to the STM key: ${key}`,
884
- inputSchema: {
1092
+ description: writeDescription,
1093
+ parameters: {
885
1094
  type: "object",
886
1095
  properties: {
887
- value: { type: "string", description: isDocument ? "New document content" : "The value to write" }
1096
+ value: { type: "string", description: customType ? `Value following ${customTypeName} schema` : isDocument ? "New document content" : "The value to write" }
888
1097
  },
889
1098
  required: ["value"]
890
1099
  },
@@ -907,7 +1116,7 @@ var AIToolBuilder = class _AIToolBuilder {
907
1116
  if (isDocument) {
908
1117
  tools[`append_${sanitizedKey}`] = {
909
1118
  description: `Append text to the end of "${key}" document`,
910
- inputSchema: {
1119
+ parameters: {
911
1120
  type: "object",
912
1121
  properties: {
913
1122
  text: { type: "string", description: "Text to append" }
@@ -932,7 +1141,7 @@ var AIToolBuilder = class _AIToolBuilder {
932
1141
  };
933
1142
  tools[`insert_${sanitizedKey}`] = {
934
1143
  description: `Insert text at a position in "${key}" document`,
935
- inputSchema: {
1144
+ parameters: {
936
1145
  type: "object",
937
1146
  properties: {
938
1147
  index: { type: "number", description: "Position to insert at (0 = start)" },
@@ -955,7 +1164,7 @@ var AIToolBuilder = class _AIToolBuilder {
955
1164
  };
956
1165
  tools[`edit_${sanitizedKey}`] = {
957
1166
  description: `Find and replace text in "${key}" document`,
958
- inputSchema: {
1167
+ parameters: {
959
1168
  type: "object",
960
1169
  properties: {
961
1170
  find: { type: "string", description: "Text to find" },
@@ -992,11 +1201,16 @@ var AIToolBuilder = class _AIToolBuilder {
992
1201
  return tools;
993
1202
  }
994
1203
  /**
995
- * Generate a system prompt containing all visible STM keys and their values.
996
- * Indicates which tools can be used to modify writable keys.
997
- */
1204
+ * Generate a system prompt containing all visible STM keys and their values.
1205
+ * Indicates which tools can be used to modify writable keys.
1206
+ */
998
1207
  static getSystemPrompt(mc) {
999
1208
  const lines = [];
1209
+ const registeredTypes = mc.getRegisteredTypes();
1210
+ if (registeredTypes.length > 0) {
1211
+ lines.push(`[create_key tool available - registered types: ${registeredTypes.join(", ")}]`);
1212
+ lines.push("");
1213
+ }
1000
1214
  for (const key of mc.keys()) {
1001
1215
  if (key.startsWith("$")) {
1002
1216
  continue;
@@ -1014,8 +1228,18 @@ var AIToolBuilder = class _AIToolBuilder {
1014
1228
  const isWritable = attributes?.systemTags?.includes("LLMWrite");
1015
1229
  const isDocument = attributes?.type === "document";
1016
1230
  const sanitizedKey = _AIToolBuilder.sanitizeKeyForTool(key);
1231
+ const customTypeName = mc.getKeyType(key);
1232
+ const customType = customTypeName ? mc.getTypeSchema(customTypeName) : void 0;
1017
1233
  if (isWritable) {
1018
- if (isDocument) {
1234
+ if (customType) {
1235
+ const schemaInfo = SchemaParser.toMarkdown(customType);
1236
+ lines.push(
1237
+ `${key} (type: ${customTypeName}): ${displayValue}
1238
+ Schema:
1239
+ ${schemaInfo}
1240
+ Tool: write_${sanitizedKey}`
1241
+ );
1242
+ } else if (isDocument) {
1019
1243
  lines.push(
1020
1244
  `${key}: ${displayValue}. Document tools: write_${sanitizedKey}, append_${sanitizedKey}, edit_${sanitizedKey}`
1021
1245
  );
@@ -1026,15 +1250,19 @@ var AIToolBuilder = class _AIToolBuilder {
1026
1250
  );
1027
1251
  }
1028
1252
  } else {
1029
- lines.push(`${key}: ${displayValue}`);
1253
+ if (customTypeName) {
1254
+ lines.push(`${key} (type: ${customTypeName}): ${displayValue}`);
1255
+ } else {
1256
+ lines.push(`${key}: ${displayValue}`);
1257
+ }
1030
1258
  }
1031
1259
  }
1032
1260
  return lines.join("\n");
1033
1261
  }
1034
1262
  /**
1035
- * Execute a tool call by name with the given value.
1036
- * Returns the result or null if tool not found.
1037
- */
1263
+ * Execute a tool call by name with the given value.
1264
+ * Returns the result or null if tool not found.
1265
+ */
1038
1266
  static executeToolCall(mc, toolName, value) {
1039
1267
  const match = toolName.match(/^(write|append|insert|edit)_(.+)$/);
1040
1268
  if (!match) {
@@ -1361,7 +1589,7 @@ var MindCache = class {
1361
1589
  listeners = {};
1362
1590
  globalListeners = [];
1363
1591
  // Metadata
1364
- version = "3.3.2";
1592
+ version = "3.6.0";
1365
1593
  // Internal flag to prevent sync loops when receiving remote updates
1366
1594
  // (Less critical with Yjs but kept for API compat)
1367
1595
  normalizeSystemTags(tags) {
@@ -1398,6 +1626,8 @@ var MindCache = class {
1398
1626
  _history = [];
1399
1627
  _historyOptions = { maxEntries: 100, snapshotInterval: 10 };
1400
1628
  _historyEnabled = false;
1629
+ // Custom type registry
1630
+ _typeRegistry = /* @__PURE__ */ new Map();
1401
1631
  constructor(options) {
1402
1632
  this.doc = options?.doc || new Y.Doc();
1403
1633
  this.rootMap = this.doc.getMap("mindcache");
@@ -2354,6 +2584,71 @@ var MindCache = class {
2354
2584
  systemGetKeysByTag(tag) {
2355
2585
  return TagManager.systemGetKeysByTag(this, tag);
2356
2586
  }
2587
+ // ============================================
2588
+ // Custom Type Methods
2589
+ // ============================================
2590
+ /**
2591
+ * Register a custom type with a markdown schema definition.
2592
+ *
2593
+ * Schema format:
2594
+ * ```
2595
+ * #TypeName
2596
+ * * fieldName: description of the field
2597
+ * * anotherField: description
2598
+ * ```
2599
+ *
2600
+ * @param name - Type name (e.g., 'Contact')
2601
+ * @param schema - Markdown schema definition
2602
+ * @throws Error if schema format is invalid
2603
+ */
2604
+ registerType(name, schema) {
2605
+ const typeDef = SchemaParser.parse(schema);
2606
+ typeDef.name = name;
2607
+ this._typeRegistry.set(name, typeDef);
2608
+ }
2609
+ /**
2610
+ * Assign a custom type to a key.
2611
+ * The key must exist and the type must be registered.
2612
+ * Also sets the underlying type to 'json' since custom types are structured JSON data.
2613
+ *
2614
+ * @param key - Key to assign type to
2615
+ * @param typeName - Registered type name
2616
+ * @throws Error if key doesn't exist or type is not registered
2617
+ */
2618
+ setType(key, typeName) {
2619
+ if (!this.rootMap.has(key)) {
2620
+ throw new Error(`Key "${key}" does not exist`);
2621
+ }
2622
+ if (!this._typeRegistry.has(typeName)) {
2623
+ throw new Error(`Type "${typeName}" is not registered. Use registerType() first.`);
2624
+ }
2625
+ this.set_attributes(key, { type: "json", customType: typeName });
2626
+ }
2627
+ /**
2628
+ * Get a registered type schema definition.
2629
+ *
2630
+ * @param typeName - Type name to look up
2631
+ * @returns The type definition or undefined if not registered
2632
+ */
2633
+ getTypeSchema(typeName) {
2634
+ return this._typeRegistry.get(typeName);
2635
+ }
2636
+ /**
2637
+ * Get all registered type names.
2638
+ */
2639
+ getRegisteredTypes() {
2640
+ return Array.from(this._typeRegistry.keys());
2641
+ }
2642
+ /**
2643
+ * Get the custom type assigned to a key.
2644
+ *
2645
+ * @param key - Key to check
2646
+ * @returns Type name or undefined if no custom type assigned
2647
+ */
2648
+ getKeyType(key) {
2649
+ const attrs = this.get_attributes(key);
2650
+ return attrs?.customType;
2651
+ }
2357
2652
  /**
2358
2653
  * Helper to get sorted keys (by zIndex).
2359
2654
  * Respects context filtering when set.
@@ -2615,9 +2910,28 @@ var MindCache = class {
2615
2910
  findKeyFromSanitizedTool(sanitizedKey) {
2616
2911
  return AIToolBuilder.findKeyFromSanitizedTool(this, sanitizedKey);
2617
2912
  }
2913
+ /**
2914
+ * Generate framework-agnostic tools with raw JSON Schema.
2915
+ * Works with: OpenAI SDK, Anthropic SDK, LangChain, and other frameworks.
2916
+ *
2917
+ * Tool format:
2918
+ * {
2919
+ * description: string,
2920
+ * parameters: { type: 'object', properties: {...}, required: [...] },
2921
+ * execute: async (args) => result
2922
+ * }
2923
+ *
2924
+ * Security: All tools use llm_set_key internally which:
2925
+ * - Only modifies VALUES, never attributes/systemTags
2926
+ * - Prevents LLMs from escalating privileges
2927
+ */
2928
+ create_tools() {
2929
+ return AIToolBuilder.createTools(this);
2930
+ }
2618
2931
  /**
2619
2932
  * Generate Vercel AI SDK compatible tools for writable keys.
2620
- * For document type keys, generates additional tools: append_, insert_, edit_
2933
+ * Wraps parameters with jsonSchema() for AI SDK v5 compatibility.
2934
+ * Use this with: generateText(), streamText() from 'ai' package.
2621
2935
  *
2622
2936
  * Security: All tools use llm_set_key internally which:
2623
2937
  * - Only modifies VALUES, never attributes/systemTags
@@ -2660,8 +2974,6 @@ init_CloudAdapter();
2660
2974
  init_CloudAdapter();
2661
2975
 
2662
2976
  // src/cloud/OAuthClient.ts
2663
- var DEFAULT_API_URL = "https://api.mindcache.dev";
2664
- var DEFAULT_USERINFO_URL = DEFAULT_API_URL + "/oauth/userinfo";
2665
2977
  var TOKEN_REFRESH_BUFFER = 5 * 60 * 1e3;
2666
2978
  function generateRandomString(length2) {
2667
2979
  const array = new Uint8Array(length2);
@@ -2690,6 +3002,21 @@ var OAuthClient = class {
2690
3002
  tokens = null;
2691
3003
  refreshPromise = null;
2692
3004
  constructor(config) {
3005
+ if (!config.baseUrl) {
3006
+ throw new Error(
3007
+ 'MindCache OAuth: baseUrl is required!\n For production: baseUrl: "https://api.mindcache.dev"\n For local dev: baseUrl: "http://localhost:8787"'
3008
+ );
3009
+ }
3010
+ try {
3011
+ const url = new URL(config.baseUrl);
3012
+ if (url.hostname === "mindcache.dev") {
3013
+ console.error(
3014
+ '\u274C MindCache OAuth ERROR: baseUrl should be "api.mindcache.dev" not "mindcache.dev"\n Current: ' + config.baseUrl + "\n Correct: https://api.mindcache.dev"
3015
+ );
3016
+ }
3017
+ } catch {
3018
+ throw new Error("MindCache OAuth: Invalid baseUrl format: " + config.baseUrl);
3019
+ }
2693
3020
  let redirectUri = config.redirectUri;
2694
3021
  if (!redirectUri && typeof window !== "undefined") {
2695
3022
  const url = new URL(window.location.href);
@@ -2697,55 +3024,54 @@ var OAuthClient = class {
2697
3024
  url.hash = "";
2698
3025
  redirectUri = url.toString();
2699
3026
  }
2700
- const baseUrl = config.baseUrl || config.apiUrl || DEFAULT_API_URL;
2701
- const authUrl = config.authUrl || baseUrl + "/oauth/authorize";
2702
- const tokenUrl = config.tokenUrl || baseUrl + "/oauth/token";
2703
- const apiUrl = config.apiUrl || baseUrl;
3027
+ const baseUrl = config.baseUrl.replace(/\/$/, "");
2704
3028
  this.config = {
2705
3029
  clientId: config.clientId,
3030
+ baseUrl,
2706
3031
  redirectUri: redirectUri || "",
2707
3032
  scopes: config.scopes || ["read", "write"],
2708
- baseUrl,
2709
- authUrl,
2710
- tokenUrl,
2711
- apiUrl,
2712
3033
  usePKCE: config.usePKCE !== false,
2713
3034
  // Default true
2714
3035
  storagePrefix: config.storagePrefix || "mindcache_oauth"
2715
3036
  };
2716
- console.log("\u{1F510} MindCache OAuth Config:", {
3037
+ console.log("\u{1F510} MindCache OAuth:", {
2717
3038
  baseUrl: this.config.baseUrl,
2718
- authUrl: this.config.authUrl,
2719
- tokenUrl: this.config.tokenUrl,
2720
- apiUrl: this.config.apiUrl,
3039
+ authUrl: this.authUrl,
3040
+ tokenUrl: this.tokenUrl,
2721
3041
  clientId: this.config.clientId.substring(0, 20) + "..."
2722
3042
  });
2723
3043
  this.validateApi();
2724
3044
  this.loadTokens();
2725
3045
  }
3046
+ /** Derived auth URL */
3047
+ get authUrl() {
3048
+ return this.config.baseUrl + "/oauth/authorize";
3049
+ }
3050
+ /** Derived token URL */
3051
+ get tokenUrl() {
3052
+ return this.config.baseUrl + "/oauth/token";
3053
+ }
3054
+ /** Derived userinfo URL */
3055
+ get userinfoUrl() {
3056
+ return this.config.baseUrl + "/oauth/userinfo";
3057
+ }
2726
3058
  /**
2727
- * Validate the API is reachable and warn about common misconfigurations
3059
+ * Validate the API is reachable
2728
3060
  */
2729
3061
  async validateApi() {
2730
3062
  try {
2731
- const response = await fetch(`${this.config.apiUrl}/oauth/apps/info`, {
3063
+ const response = await fetch(`${this.config.baseUrl}/oauth/apps/info`, {
2732
3064
  method: "GET",
2733
3065
  headers: { "Accept": "application/json" }
2734
3066
  });
2735
3067
  if (response.status === 404) {
2736
3068
  console.error(
2737
- "\u274C MindCache OAuth ERROR: API not found at " + this.config.apiUrl + '\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'
2738
- );
2739
- } else if (!response.ok) {
2740
- console.warn(
2741
- "\u26A0\uFE0F MindCache OAuth: API responded with status " + response.status + "\n URL: " + this.config.apiUrl
3069
+ "\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'
2742
3070
  );
2743
- } else {
2744
- console.log("\u2705 MindCache OAuth: API is reachable at " + this.config.apiUrl);
2745
3071
  }
2746
3072
  } catch (error) {
2747
3073
  console.error(
2748
- "\u274C MindCache OAuth ERROR: Cannot reach API at " + this.config.apiUrl + "\n Error: " + (error instanceof Error ? error.message : String(error)) + "\n Check your network connection and baseUrl configuration."
3074
+ "\u274C MindCache OAuth ERROR: Cannot reach API at " + this.config.baseUrl + "\n Error: " + (error instanceof Error ? error.message : String(error))
2749
3075
  );
2750
3076
  }
2751
3077
  }
@@ -2774,7 +3100,7 @@ var OAuthClient = class {
2774
3100
  async authorize(options) {
2775
3101
  const state = options?.state || generateRandomString(32);
2776
3102
  this.setStorage("state", state);
2777
- const url = new URL(this.config.authUrl);
3103
+ const url = new URL(this.authUrl);
2778
3104
  url.searchParams.set("response_type", "code");
2779
3105
  url.searchParams.set("client_id", this.config.clientId);
2780
3106
  url.searchParams.set("redirect_uri", this.config.redirectUri);
@@ -2837,7 +3163,7 @@ var OAuthClient = class {
2837
3163
  }
2838
3164
  body.code_verifier = codeVerifier;
2839
3165
  }
2840
- const response = await fetch(this.config.tokenUrl, {
3166
+ const response = await fetch(this.tokenUrl, {
2841
3167
  method: "POST",
2842
3168
  headers: {
2843
3169
  "Content-Type": "application/json"
@@ -2889,7 +3215,7 @@ var OAuthClient = class {
2889
3215
  throw new Error("No refresh token available");
2890
3216
  }
2891
3217
  try {
2892
- const response = await fetch(this.config.tokenUrl, {
3218
+ const response = await fetch(this.tokenUrl, {
2893
3219
  method: "POST",
2894
3220
  headers: {
2895
3221
  "Content-Type": "application/json"
@@ -2923,7 +3249,7 @@ var OAuthClient = class {
2923
3249
  */
2924
3250
  async getUserInfo() {
2925
3251
  const token = await this.getAccessToken();
2926
- const response = await fetch(DEFAULT_USERINFO_URL, {
3252
+ const response = await fetch(this.userinfoUrl, {
2927
3253
  headers: {
2928
3254
  Authorization: `Bearer ${token}`
2929
3255
  }
@@ -2945,7 +3271,7 @@ var OAuthClient = class {
2945
3271
  async logout() {
2946
3272
  if (this.tokens?.accessToken) {
2947
3273
  try {
2948
- await fetch(this.config.tokenUrl.replace("/token", "/revoke"), {
3274
+ await fetch(this.tokenUrl.replace("/token", "/revoke"), {
2949
3275
  method: "POST",
2950
3276
  headers: {
2951
3277
  "Content-Type": "application/json"
@@ -2968,7 +3294,7 @@ var OAuthClient = class {
2968
3294
  }
2969
3295
  /**
2970
3296
  * Token provider for MindCache cloud config
2971
- * This fetches a WebSocket token (short-lived) using the OAuth access token
3297
+ * This fetches a WebSocket token using the OAuth access token
2972
3298
  * Use this with MindCacheCloudOptions.tokenProvider
2973
3299
  */
2974
3300
  tokenProvider = async () => {
@@ -2977,7 +3303,7 @@ var OAuthClient = class {
2977
3303
  if (!instanceId) {
2978
3304
  throw new Error("No instance ID available. Complete OAuth flow first.");
2979
3305
  }
2980
- const response = await fetch(`${this.config.apiUrl}/api/ws-token`, {
3306
+ const response = await fetch(`${this.config.baseUrl}/api/ws-token`, {
2981
3307
  method: "POST",
2982
3308
  headers: {
2983
3309
  "Content-Type": "application/json",
@@ -2997,7 +3323,6 @@ var OAuthClient = class {
2997
3323
  };
2998
3324
  /**
2999
3325
  * Get raw OAuth access token (for API calls, not WebSocket)
3000
- * Use getAccessToken() for most cases
3001
3326
  */
3002
3327
  accessTokenProvider = async () => {
3003
3328
  return this.getAccessToken();
@@ -3082,10 +3407,1017 @@ function useMindCache(options) {
3082
3407
  error
3083
3408
  };
3084
3409
  }
3410
+ function createModel(provider, model, apiKey) {
3411
+ switch (provider) {
3412
+ case "openai": {
3413
+ const openai = createOpenAI({ apiKey });
3414
+ return openai(model);
3415
+ }
3416
+ case "anthropic":
3417
+ throw new Error("Anthropic provider not yet implemented. Use modelProvider for custom providers.");
3418
+ default:
3419
+ throw new Error(`Unknown provider: ${provider}. Use modelProvider for custom providers.`);
3420
+ }
3421
+ }
3422
+ var MindCacheContext = createContext(null);
3423
+ function useMindCacheContext() {
3424
+ const context = useContext(MindCacheContext);
3425
+ if (!context) {
3426
+ throw new Error("useMindCacheContext must be used within a MindCacheProvider");
3427
+ }
3428
+ return context;
3429
+ }
3430
+ function MindCacheProvider({
3431
+ mindcache: mcOptions,
3432
+ sync: syncConfig,
3433
+ ai: aiConfig = {},
3434
+ children
3435
+ }) {
3436
+ const [mindcache2, setMindcache] = useState(null);
3437
+ const [isLoaded, setIsLoaded] = useState(false);
3438
+ const [error, setError] = useState(null);
3439
+ const [hasApiKey, setHasApiKey] = useState(false);
3440
+ const [lastSyncAt, setLastSyncAt] = useState(null);
3441
+ const [isSyncing, setIsSyncing] = useState(false);
3442
+ const initRef = useRef(false);
3443
+ const resolvedAiConfig = {
3444
+ provider: "openai",
3445
+ model: "gpt-4o",
3446
+ keyStorage: "localStorage",
3447
+ storageKey: "ai_api_key",
3448
+ ...aiConfig
3449
+ };
3450
+ useEffect(() => {
3451
+ if (initRef.current) {
3452
+ return;
3453
+ }
3454
+ initRef.current = true;
3455
+ const init = async () => {
3456
+ try {
3457
+ const options = mcOptions || {
3458
+ indexedDB: {
3459
+ dbName: "mindcache_local_first",
3460
+ storeName: "mindcache_store",
3461
+ debounceMs: 1e3
3462
+ }
3463
+ };
3464
+ const mc = new MindCache(options);
3465
+ await mc.waitForSync();
3466
+ setMindcache(mc);
3467
+ setIsLoaded(true);
3468
+ if (resolvedAiConfig.keyStorage === "localStorage" && typeof window !== "undefined") {
3469
+ const stored = localStorage.getItem(resolvedAiConfig.storageKey || "openai_api_key");
3470
+ setHasApiKey(!!stored);
3471
+ } else if (resolvedAiConfig.apiKey) {
3472
+ setHasApiKey(true);
3473
+ }
3474
+ } catch (err) {
3475
+ setError(err instanceof Error ? err : new Error(String(err)));
3476
+ setIsLoaded(true);
3477
+ }
3478
+ };
3479
+ init();
3480
+ return () => {
3481
+ if (mindcache2) {
3482
+ mindcache2.disconnect();
3483
+ }
3484
+ };
3485
+ }, []);
3486
+ const getApiKey = () => {
3487
+ if (resolvedAiConfig.apiKey) {
3488
+ return resolvedAiConfig.apiKey;
3489
+ }
3490
+ if (resolvedAiConfig.keyStorage === "localStorage" && typeof window !== "undefined") {
3491
+ return localStorage.getItem(resolvedAiConfig.storageKey || "openai_api_key");
3492
+ }
3493
+ return null;
3494
+ };
3495
+ const setApiKey = (key) => {
3496
+ if (resolvedAiConfig.keyStorage === "localStorage" && typeof window !== "undefined") {
3497
+ localStorage.setItem(resolvedAiConfig.storageKey || "openai_api_key", key);
3498
+ setHasApiKey(true);
3499
+ }
3500
+ };
3501
+ const getModel = () => {
3502
+ const apiKey = getApiKey();
3503
+ if (!apiKey) {
3504
+ throw new Error("API key not configured. Call setApiKey() first or configure ai.apiKey.");
3505
+ }
3506
+ if (resolvedAiConfig.modelProvider) {
3507
+ return resolvedAiConfig.modelProvider(apiKey);
3508
+ }
3509
+ const provider = resolvedAiConfig.provider || "openai";
3510
+ const model = resolvedAiConfig.model || "gpt-4o";
3511
+ return createModel(provider, model, apiKey);
3512
+ };
3513
+ const syncToGitStore = async () => {
3514
+ if (!mindcache2 || !syncConfig?.gitstore) {
3515
+ return;
3516
+ }
3517
+ setIsSyncing(true);
3518
+ try {
3519
+ let gitStoreModule;
3520
+ try {
3521
+ gitStoreModule = await Function('return import("@mindcache/gitstore")')();
3522
+ } catch {
3523
+ throw new Error("@mindcache/gitstore is not installed. Run: npm install @mindcache/gitstore");
3524
+ }
3525
+ const { GitStore, MindCacheSync } = gitStoreModule;
3526
+ const token = typeof syncConfig.gitstore.token === "function" ? await syncConfig.gitstore.token() : syncConfig.gitstore.token;
3527
+ const gitStore = new GitStore({
3528
+ owner: syncConfig.gitstore.owner,
3529
+ repo: syncConfig.gitstore.repo,
3530
+ tokenProvider: async () => token
3531
+ });
3532
+ const sync = new MindCacheSync(gitStore, mindcache2, {
3533
+ filePath: syncConfig.gitstore.path || "mindcache.md"
3534
+ });
3535
+ await sync.save({ message: "Auto-sync from MindCache" });
3536
+ setLastSyncAt(/* @__PURE__ */ new Date());
3537
+ } catch (err) {
3538
+ console.error("[MindCacheProvider] Sync error:", err);
3539
+ throw err;
3540
+ } finally {
3541
+ setIsSyncing(false);
3542
+ }
3543
+ };
3544
+ const value = {
3545
+ mindcache: mindcache2,
3546
+ isLoaded,
3547
+ error,
3548
+ aiConfig: resolvedAiConfig,
3549
+ syncConfig,
3550
+ getApiKey,
3551
+ setApiKey,
3552
+ hasApiKey,
3553
+ getModel,
3554
+ syncToGitStore,
3555
+ lastSyncAt,
3556
+ isSyncing
3557
+ };
3558
+ return /* @__PURE__ */ jsx(MindCacheContext.Provider, { value, children });
3559
+ }
3560
+ function generateId() {
3561
+ return Math.random().toString(36).substring(2, 15) + Date.now().toString(36);
3562
+ }
3563
+ function useClientChat(options = {}) {
3564
+ const context = useMindCacheContext();
3565
+ const mc = options.mindcache || context.mindcache;
3566
+ const [messages, setMessages] = useState(options.initialMessages || []);
3567
+ const [status, setStatus] = useState("idle");
3568
+ const [error, setError] = useState(null);
3569
+ const [streamingContent, setStreamingContent] = useState("");
3570
+ const abortControllerRef = useRef(null);
3571
+ const {
3572
+ systemPrompt,
3573
+ onMindCacheChange,
3574
+ onFinish,
3575
+ onError,
3576
+ maxToolCalls = 5
3577
+ } = options;
3578
+ const apiKey = context.getApiKey();
3579
+ const addMessage = useCallback((msg) => {
3580
+ const newMessage = {
3581
+ ...msg,
3582
+ id: generateId(),
3583
+ createdAt: /* @__PURE__ */ new Date()
3584
+ };
3585
+ setMessages((prev) => [...prev, newMessage]);
3586
+ return newMessage;
3587
+ }, []);
3588
+ const clearMessages = useCallback(() => {
3589
+ setMessages([]);
3590
+ setError(null);
3591
+ setStreamingContent("");
3592
+ }, []);
3593
+ const stop = useCallback(() => {
3594
+ abortControllerRef.current?.abort();
3595
+ setStatus("idle");
3596
+ }, []);
3597
+ const sendMessage = useCallback(async (content) => {
3598
+ if (!mc) {
3599
+ const err = new Error("MindCache not initialized");
3600
+ setError(err);
3601
+ onError?.(err);
3602
+ return;
3603
+ }
3604
+ if (!apiKey) {
3605
+ const err = new Error("API key not configured. Please set your API key.");
3606
+ setError(err);
3607
+ onError?.(err);
3608
+ return;
3609
+ }
3610
+ abortControllerRef.current?.abort();
3611
+ abortControllerRef.current = new AbortController();
3612
+ const userMessage = addMessage({ role: "user", content });
3613
+ setStatus("loading");
3614
+ setError(null);
3615
+ setStreamingContent("");
3616
+ try {
3617
+ const model = context.getModel();
3618
+ const finalSystemPrompt = systemPrompt || mc.get_system_prompt();
3619
+ const tools = mc.create_vercel_ai_tools();
3620
+ const apiMessages = messages.concat(userMessage).map((m) => ({
3621
+ role: m.role,
3622
+ content: m.content
3623
+ }));
3624
+ setStatus("streaming");
3625
+ const parts = [];
3626
+ let accumulatedText = "";
3627
+ const result = await streamText({
3628
+ model,
3629
+ system: finalSystemPrompt,
3630
+ messages: apiMessages,
3631
+ tools,
3632
+ stopWhen: stepCountIs(maxToolCalls),
3633
+ abortSignal: abortControllerRef.current.signal,
3634
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
3635
+ onStepFinish: async (step) => {
3636
+ if (step.toolCalls && step.toolCalls.length > 0) {
3637
+ for (const toolCall of step.toolCalls) {
3638
+ const toolName = toolCall.toolName;
3639
+ const args = toolCall.args || toolCall.input || {};
3640
+ parts.push({
3641
+ type: "tool-call",
3642
+ toolCallId: toolCall.toolCallId,
3643
+ toolName,
3644
+ args
3645
+ });
3646
+ if (typeof toolName === "string" && (toolName.startsWith("write_") || toolName === "create_key")) {
3647
+ const value = args.value;
3648
+ const result2 = mc.executeToolCall(toolName, value);
3649
+ parts.push({
3650
+ type: "tool-result",
3651
+ toolCallId: toolCall.toolCallId,
3652
+ toolName,
3653
+ result: result2
3654
+ });
3655
+ onMindCacheChange?.();
3656
+ }
3657
+ }
3658
+ }
3659
+ if (step.text) {
3660
+ accumulatedText += step.text;
3661
+ }
3662
+ }
3663
+ });
3664
+ for await (const chunk of result.textStream) {
3665
+ accumulatedText += chunk;
3666
+ setStreamingContent(accumulatedText);
3667
+ }
3668
+ if (accumulatedText) {
3669
+ parts.unshift({ type: "text", text: accumulatedText });
3670
+ }
3671
+ const assistantMessage = {
3672
+ id: generateId(),
3673
+ role: "assistant",
3674
+ content: accumulatedText,
3675
+ parts: parts.length > 0 ? parts : void 0,
3676
+ createdAt: /* @__PURE__ */ new Date()
3677
+ };
3678
+ setMessages((prev) => [...prev, assistantMessage]);
3679
+ setStreamingContent("");
3680
+ setStatus("idle");
3681
+ onFinish?.(assistantMessage);
3682
+ } catch (err) {
3683
+ if (err.name === "AbortError") {
3684
+ if (streamingContent) {
3685
+ const partialMessage = {
3686
+ id: generateId(),
3687
+ role: "assistant",
3688
+ content: streamingContent + " [stopped]",
3689
+ createdAt: /* @__PURE__ */ new Date()
3690
+ };
3691
+ setMessages((prev) => [...prev, partialMessage]);
3692
+ }
3693
+ setStreamingContent("");
3694
+ setStatus("idle");
3695
+ return;
3696
+ }
3697
+ const error2 = err instanceof Error ? err : new Error(String(err));
3698
+ setError(error2);
3699
+ setStatus("error");
3700
+ setStreamingContent("");
3701
+ onError?.(error2);
3702
+ }
3703
+ }, [
3704
+ mc,
3705
+ apiKey,
3706
+ context,
3707
+ messages,
3708
+ systemPrompt,
3709
+ maxToolCalls,
3710
+ addMessage,
3711
+ onMindCacheChange,
3712
+ onFinish,
3713
+ onError,
3714
+ streamingContent
3715
+ ]);
3716
+ useEffect(() => {
3717
+ return () => {
3718
+ abortControllerRef.current?.abort();
3719
+ };
3720
+ }, []);
3721
+ return {
3722
+ messages,
3723
+ status,
3724
+ error,
3725
+ sendMessage,
3726
+ clearMessages,
3727
+ isLoading: status === "loading" || status === "streaming",
3728
+ addMessage,
3729
+ stop,
3730
+ streamingContent
3731
+ };
3732
+ }
3733
+ var defaultTheme = {
3734
+ background: "#000",
3735
+ userBubble: "#1a1a2e",
3736
+ assistantBubble: "#0d0d0d",
3737
+ textColor: "#22c55e",
3738
+ secondaryTextColor: "#6b7280",
3739
+ borderColor: "#333",
3740
+ primaryColor: "#22c55e",
3741
+ fontFamily: '-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif'
3742
+ };
3743
+ function DefaultMessage({
3744
+ message,
3745
+ theme
3746
+ }) {
3747
+ const isUser = message.role === "user";
3748
+ return /* @__PURE__ */ jsx(
3749
+ "div",
3750
+ {
3751
+ style: {
3752
+ display: "flex",
3753
+ justifyContent: isUser ? "flex-end" : "flex-start",
3754
+ marginBottom: "12px"
3755
+ },
3756
+ children: /* @__PURE__ */ jsxs(
3757
+ "div",
3758
+ {
3759
+ style: {
3760
+ maxWidth: "80%",
3761
+ padding: "12px 16px",
3762
+ borderRadius: "12px",
3763
+ backgroundColor: isUser ? theme.userBubble : theme.assistantBubble,
3764
+ border: `1px solid ${theme.borderColor}`
3765
+ },
3766
+ children: [
3767
+ /* @__PURE__ */ jsx(
3768
+ "div",
3769
+ {
3770
+ style: {
3771
+ fontSize: "10px",
3772
+ color: theme.secondaryTextColor,
3773
+ marginBottom: "4px",
3774
+ textTransform: "uppercase"
3775
+ },
3776
+ children: isUser ? "You" : "Assistant"
3777
+ }
3778
+ ),
3779
+ /* @__PURE__ */ jsx(
3780
+ "div",
3781
+ {
3782
+ style: {
3783
+ color: theme.textColor,
3784
+ whiteSpace: "pre-wrap",
3785
+ wordBreak: "break-word",
3786
+ lineHeight: 1.5
3787
+ },
3788
+ children: message.content
3789
+ }
3790
+ )
3791
+ ]
3792
+ }
3793
+ )
3794
+ }
3795
+ );
3796
+ }
3797
+ function ApiKeyInput({
3798
+ theme,
3799
+ onSubmit
3800
+ }) {
3801
+ const [key, setKey] = useState("");
3802
+ const handleSubmit = (e) => {
3803
+ e.preventDefault();
3804
+ if (key.trim()) {
3805
+ onSubmit(key.trim());
3806
+ }
3807
+ };
3808
+ return /* @__PURE__ */ jsxs(
3809
+ "div",
3810
+ {
3811
+ style: {
3812
+ display: "flex",
3813
+ flexDirection: "column",
3814
+ alignItems: "center",
3815
+ justifyContent: "center",
3816
+ height: "100%",
3817
+ padding: "20px"
3818
+ },
3819
+ children: [
3820
+ /* @__PURE__ */ jsx(
3821
+ "div",
3822
+ {
3823
+ style: {
3824
+ fontSize: "14px",
3825
+ color: theme.textColor,
3826
+ marginBottom: "16px",
3827
+ textAlign: "center"
3828
+ },
3829
+ children: "Enter your API key to start chatting"
3830
+ }
3831
+ ),
3832
+ /* @__PURE__ */ jsxs("form", { onSubmit: handleSubmit, style: { width: "100%", maxWidth: "400px" }, children: [
3833
+ /* @__PURE__ */ jsx(
3834
+ "input",
3835
+ {
3836
+ type: "password",
3837
+ value: key,
3838
+ onChange: (e) => setKey(e.target.value),
3839
+ placeholder: "sk-...",
3840
+ style: {
3841
+ width: "100%",
3842
+ padding: "12px",
3843
+ backgroundColor: theme.assistantBubble,
3844
+ border: `1px solid ${theme.borderColor}`,
3845
+ borderRadius: "8px",
3846
+ color: theme.textColor,
3847
+ fontFamily: theme.fontFamily,
3848
+ fontSize: "14px",
3849
+ marginBottom: "12px"
3850
+ }
3851
+ }
3852
+ ),
3853
+ /* @__PURE__ */ jsx(
3854
+ "button",
3855
+ {
3856
+ type: "submit",
3857
+ disabled: !key.trim(),
3858
+ style: {
3859
+ width: "100%",
3860
+ padding: "12px",
3861
+ backgroundColor: theme.primaryColor,
3862
+ border: "none",
3863
+ borderRadius: "8px",
3864
+ color: "#000",
3865
+ fontFamily: theme.fontFamily,
3866
+ fontSize: "14px",
3867
+ fontWeight: "bold",
3868
+ cursor: key.trim() ? "pointer" : "not-allowed",
3869
+ opacity: key.trim() ? 1 : 0.5
3870
+ },
3871
+ children: "Save & Start"
3872
+ }
3873
+ )
3874
+ ] }),
3875
+ /* @__PURE__ */ jsx(
3876
+ "div",
3877
+ {
3878
+ style: {
3879
+ fontSize: "11px",
3880
+ color: theme.secondaryTextColor,
3881
+ marginTop: "16px",
3882
+ textAlign: "center"
3883
+ },
3884
+ children: "Your key is stored locally and never sent to our servers."
3885
+ }
3886
+ )
3887
+ ]
3888
+ }
3889
+ );
3890
+ }
3891
+ function MindCacheChat({
3892
+ theme: customTheme,
3893
+ placeholder = "Type a message...",
3894
+ welcomeMessage = "Hello! I'm ready to help you.",
3895
+ showApiKeyInput = true,
3896
+ className,
3897
+ style,
3898
+ renderMessage,
3899
+ header,
3900
+ footer,
3901
+ initialMessages,
3902
+ ...chatOptions
3903
+ }) {
3904
+ const context = useMindCacheContext();
3905
+ const theme = { ...defaultTheme, ...customTheme };
3906
+ const messagesEndRef = useRef(null);
3907
+ const inputRef = useRef(null);
3908
+ const [inputValue, setInputValue] = useState("");
3909
+ const defaultInitialMessages = welcomeMessage ? [
3910
+ {
3911
+ id: "welcome",
3912
+ role: "assistant",
3913
+ content: welcomeMessage,
3914
+ createdAt: /* @__PURE__ */ new Date()
3915
+ }
3916
+ ] : [];
3917
+ const {
3918
+ messages,
3919
+ sendMessage,
3920
+ isLoading,
3921
+ error,
3922
+ streamingContent,
3923
+ stop
3924
+ } = useClientChat({
3925
+ ...chatOptions,
3926
+ initialMessages: initialMessages || defaultInitialMessages,
3927
+ mindcache: context.mindcache || void 0
3928
+ });
3929
+ useEffect(() => {
3930
+ messagesEndRef.current?.scrollIntoView({ behavior: "smooth" });
3931
+ }, [messages]);
3932
+ const handleSubmit = async (e) => {
3933
+ e?.preventDefault();
3934
+ if (!inputValue.trim() || isLoading) {
3935
+ return;
3936
+ }
3937
+ const message = inputValue.trim();
3938
+ setInputValue("");
3939
+ await sendMessage(message);
3940
+ };
3941
+ const handleKeyDown = (e) => {
3942
+ if (e.key === "Enter" && !e.shiftKey) {
3943
+ e.preventDefault();
3944
+ handleSubmit();
3945
+ }
3946
+ };
3947
+ if (showApiKeyInput && !context.hasApiKey && context.aiConfig.keyStorage !== "memory") {
3948
+ return /* @__PURE__ */ jsxs(
3949
+ "div",
3950
+ {
3951
+ className,
3952
+ style: {
3953
+ display: "flex",
3954
+ flexDirection: "column",
3955
+ height: "100%",
3956
+ backgroundColor: theme.background,
3957
+ fontFamily: theme.fontFamily,
3958
+ ...style
3959
+ },
3960
+ children: [
3961
+ header,
3962
+ /* @__PURE__ */ jsx(
3963
+ ApiKeyInput,
3964
+ {
3965
+ theme,
3966
+ onSubmit: (key) => context.setApiKey(key)
3967
+ }
3968
+ ),
3969
+ footer
3970
+ ]
3971
+ }
3972
+ );
3973
+ }
3974
+ if (!context.isLoaded) {
3975
+ return /* @__PURE__ */ jsx(
3976
+ "div",
3977
+ {
3978
+ className,
3979
+ style: {
3980
+ display: "flex",
3981
+ alignItems: "center",
3982
+ justifyContent: "center",
3983
+ height: "100%",
3984
+ backgroundColor: theme.background,
3985
+ color: theme.textColor,
3986
+ fontFamily: theme.fontFamily,
3987
+ ...style
3988
+ },
3989
+ children: "Loading..."
3990
+ }
3991
+ );
3992
+ }
3993
+ return /* @__PURE__ */ jsxs(
3994
+ "div",
3995
+ {
3996
+ className,
3997
+ style: {
3998
+ display: "flex",
3999
+ flexDirection: "column",
4000
+ height: "100%",
4001
+ backgroundColor: theme.background,
4002
+ fontFamily: theme.fontFamily,
4003
+ ...style
4004
+ },
4005
+ children: [
4006
+ header,
4007
+ /* @__PURE__ */ jsxs(
4008
+ "div",
4009
+ {
4010
+ style: {
4011
+ flex: 1,
4012
+ overflowY: "auto",
4013
+ padding: "16px"
4014
+ },
4015
+ children: [
4016
+ messages.map((message) => renderMessage ? /* @__PURE__ */ jsx(React2.Fragment, { children: renderMessage(message) }, message.id) : /* @__PURE__ */ jsx(DefaultMessage, { message, theme }, message.id)),
4017
+ streamingContent && /* @__PURE__ */ jsx(
4018
+ "div",
4019
+ {
4020
+ style: {
4021
+ display: "flex",
4022
+ justifyContent: "flex-start",
4023
+ marginBottom: "12px"
4024
+ },
4025
+ children: /* @__PURE__ */ jsxs(
4026
+ "div",
4027
+ {
4028
+ style: {
4029
+ maxWidth: "80%",
4030
+ padding: "12px 16px",
4031
+ borderRadius: "12px",
4032
+ backgroundColor: theme.assistantBubble,
4033
+ border: `1px solid ${theme.borderColor}`
4034
+ },
4035
+ children: [
4036
+ /* @__PURE__ */ jsx(
4037
+ "div",
4038
+ {
4039
+ style: {
4040
+ fontSize: "10px",
4041
+ color: theme.secondaryTextColor,
4042
+ marginBottom: "4px",
4043
+ textTransform: "uppercase"
4044
+ },
4045
+ children: "Assistant"
4046
+ }
4047
+ ),
4048
+ /* @__PURE__ */ jsxs(
4049
+ "div",
4050
+ {
4051
+ style: {
4052
+ color: theme.textColor,
4053
+ whiteSpace: "pre-wrap",
4054
+ wordBreak: "break-word",
4055
+ lineHeight: 1.5
4056
+ },
4057
+ children: [
4058
+ streamingContent,
4059
+ /* @__PURE__ */ jsx("span", { style: { opacity: 0.5, animation: "blink 1s infinite" }, children: "\u258A" })
4060
+ ]
4061
+ }
4062
+ )
4063
+ ]
4064
+ }
4065
+ )
4066
+ }
4067
+ ),
4068
+ isLoading && !streamingContent && /* @__PURE__ */ jsx(
4069
+ "div",
4070
+ {
4071
+ style: {
4072
+ display: "flex",
4073
+ justifyContent: "flex-start",
4074
+ marginBottom: "12px"
4075
+ },
4076
+ children: /* @__PURE__ */ jsx(
4077
+ "div",
4078
+ {
4079
+ style: {
4080
+ padding: "12px 16px",
4081
+ borderRadius: "12px",
4082
+ backgroundColor: theme.assistantBubble,
4083
+ border: `1px solid ${theme.borderColor}`,
4084
+ color: theme.secondaryTextColor
4085
+ },
4086
+ children: "Thinking..."
4087
+ }
4088
+ )
4089
+ }
4090
+ ),
4091
+ error && /* @__PURE__ */ jsxs(
4092
+ "div",
4093
+ {
4094
+ style: {
4095
+ padding: "12px",
4096
+ marginBottom: "12px",
4097
+ borderRadius: "8px",
4098
+ backgroundColor: "rgba(239, 68, 68, 0.1)",
4099
+ border: "1px solid rgba(239, 68, 68, 0.3)",
4100
+ color: "#ef4444",
4101
+ fontSize: "13px"
4102
+ },
4103
+ children: [
4104
+ "Error: ",
4105
+ error.message
4106
+ ]
4107
+ }
4108
+ ),
4109
+ /* @__PURE__ */ jsx("div", { ref: messagesEndRef })
4110
+ ]
4111
+ }
4112
+ ),
4113
+ /* @__PURE__ */ jsx(
4114
+ "form",
4115
+ {
4116
+ onSubmit: handleSubmit,
4117
+ style: {
4118
+ padding: "12px 16px",
4119
+ borderTop: `1px solid ${theme.borderColor}`
4120
+ },
4121
+ children: /* @__PURE__ */ jsxs(
4122
+ "div",
4123
+ {
4124
+ style: {
4125
+ display: "flex",
4126
+ gap: "8px",
4127
+ alignItems: "flex-end"
4128
+ },
4129
+ children: [
4130
+ /* @__PURE__ */ jsx(
4131
+ "textarea",
4132
+ {
4133
+ ref: inputRef,
4134
+ value: inputValue,
4135
+ onChange: (e) => {
4136
+ setInputValue(e.target.value);
4137
+ e.target.style.height = "auto";
4138
+ e.target.style.height = Math.min(e.target.scrollHeight, 120) + "px";
4139
+ },
4140
+ onKeyDown: handleKeyDown,
4141
+ placeholder: isLoading ? "Waiting for response..." : placeholder,
4142
+ disabled: isLoading,
4143
+ rows: 1,
4144
+ style: {
4145
+ flex: 1,
4146
+ padding: "12px",
4147
+ backgroundColor: theme.assistantBubble,
4148
+ border: `1px solid ${theme.borderColor}`,
4149
+ borderRadius: "8px",
4150
+ color: theme.textColor,
4151
+ fontFamily: theme.fontFamily,
4152
+ fontSize: "16px",
4153
+ // Prevents iOS zoom on focus
4154
+ resize: "none",
4155
+ minHeight: "44px",
4156
+ // Apple touch target minimum
4157
+ maxHeight: "120px",
4158
+ outline: "none",
4159
+ WebkitAppearance: "none"
4160
+ // Remove iOS styling
4161
+ }
4162
+ }
4163
+ ),
4164
+ isLoading ? /* @__PURE__ */ jsx(
4165
+ "button",
4166
+ {
4167
+ type: "button",
4168
+ onClick: stop,
4169
+ style: {
4170
+ padding: "12px 20px",
4171
+ minWidth: "44px",
4172
+ // Touch target
4173
+ minHeight: "44px",
4174
+ // Touch target
4175
+ backgroundColor: "#ef4444",
4176
+ border: "none",
4177
+ borderRadius: "8px",
4178
+ color: "#fff",
4179
+ fontFamily: theme.fontFamily,
4180
+ fontSize: "16px",
4181
+ fontWeight: "bold",
4182
+ cursor: "pointer",
4183
+ transition: "opacity 0.2s",
4184
+ WebkitTapHighlightColor: "transparent",
4185
+ touchAction: "manipulation"
4186
+ // Faster touch response
4187
+ },
4188
+ children: "Stop"
4189
+ }
4190
+ ) : /* @__PURE__ */ jsx(
4191
+ "button",
4192
+ {
4193
+ type: "submit",
4194
+ disabled: !inputValue.trim(),
4195
+ style: {
4196
+ padding: "12px 20px",
4197
+ minWidth: "44px",
4198
+ // Touch target
4199
+ minHeight: "44px",
4200
+ // Touch target
4201
+ backgroundColor: theme.primaryColor,
4202
+ border: "none",
4203
+ borderRadius: "8px",
4204
+ color: "#000",
4205
+ fontFamily: theme.fontFamily,
4206
+ fontSize: "16px",
4207
+ fontWeight: "bold",
4208
+ cursor: !inputValue.trim() ? "not-allowed" : "pointer",
4209
+ opacity: !inputValue.trim() ? 0.5 : 1,
4210
+ transition: "opacity 0.2s",
4211
+ WebkitTapHighlightColor: "transparent",
4212
+ touchAction: "manipulation"
4213
+ // Faster touch response
4214
+ },
4215
+ children: "Send"
4216
+ }
4217
+ )
4218
+ ]
4219
+ }
4220
+ )
4221
+ }
4222
+ ),
4223
+ footer
4224
+ ]
4225
+ }
4226
+ );
4227
+ }
4228
+ function useLocalFirstSync(options) {
4229
+ const {
4230
+ mindcache: mindcache2,
4231
+ gitstore,
4232
+ server,
4233
+ autoSyncInterval = 0,
4234
+ saveDebounceMs = 5e3,
4235
+ loadOnMount = true,
4236
+ mergeOnLoad = true
4237
+ } = options;
4238
+ const [status, setStatus] = useState("idle");
4239
+ const [error, setError] = useState(null);
4240
+ const [lastSyncAt, setLastSyncAt] = useState(null);
4241
+ const [hasLocalChanges, setHasLocalChanges] = useState(false);
4242
+ const saveTimeoutRef = useRef(null);
4243
+ const syncIntervalRef = useRef(null);
4244
+ const mountedRef = useRef(true);
4245
+ const getToken = useCallback(async () => {
4246
+ if (!gitstore) {
4247
+ throw new Error("GitStore not configured");
4248
+ }
4249
+ return typeof gitstore.token === "function" ? await gitstore.token() : gitstore.token;
4250
+ }, [gitstore]);
4251
+ const load = useCallback(async () => {
4252
+ if (!mindcache2) {
4253
+ return;
4254
+ }
4255
+ setStatus("loading");
4256
+ setError(null);
4257
+ try {
4258
+ if (gitstore) {
4259
+ let gitStoreModule;
4260
+ try {
4261
+ gitStoreModule = await Function('return import("@mindcache/gitstore")')();
4262
+ } catch {
4263
+ throw new Error("@mindcache/gitstore is not installed. Run: npm install @mindcache/gitstore");
4264
+ }
4265
+ const { GitStore, MindCacheSync } = gitStoreModule;
4266
+ const token = await getToken();
4267
+ const store = new GitStore({
4268
+ owner: gitstore.owner,
4269
+ repo: gitstore.repo,
4270
+ branch: gitstore.branch,
4271
+ tokenProvider: async () => token
4272
+ });
4273
+ const sync2 = new MindCacheSync(store, mindcache2, {
4274
+ filePath: gitstore.path || "mindcache.md"
4275
+ });
4276
+ await sync2.load({ merge: mergeOnLoad });
4277
+ } else if (server) {
4278
+ const response = await fetch(server.url, {
4279
+ headers: server.authToken ? { Authorization: `Bearer ${server.authToken}` } : {}
4280
+ });
4281
+ if (response.ok) {
4282
+ const markdown = await response.text();
4283
+ mindcache2.fromMarkdown(markdown, mergeOnLoad);
4284
+ }
4285
+ }
4286
+ if (mountedRef.current) {
4287
+ setLastSyncAt(/* @__PURE__ */ new Date());
4288
+ setStatus("idle");
4289
+ }
4290
+ } catch (err) {
4291
+ if (mountedRef.current) {
4292
+ const error2 = err instanceof Error ? err : new Error(String(err));
4293
+ setError(error2);
4294
+ setStatus("error");
4295
+ }
4296
+ throw err;
4297
+ }
4298
+ }, [mindcache2, gitstore, server, getToken, mergeOnLoad]);
4299
+ const save = useCallback(async (message) => {
4300
+ if (!mindcache2) {
4301
+ return;
4302
+ }
4303
+ setStatus("saving");
4304
+ setError(null);
4305
+ try {
4306
+ if (gitstore) {
4307
+ let gitStoreModule;
4308
+ try {
4309
+ gitStoreModule = await Function('return import("@mindcache/gitstore")')();
4310
+ } catch {
4311
+ throw new Error("@mindcache/gitstore is not installed. Run: npm install @mindcache/gitstore");
4312
+ }
4313
+ const { GitStore, MindCacheSync } = gitStoreModule;
4314
+ const token = await getToken();
4315
+ const store = new GitStore({
4316
+ owner: gitstore.owner,
4317
+ repo: gitstore.repo,
4318
+ branch: gitstore.branch,
4319
+ tokenProvider: async () => token
4320
+ });
4321
+ const sync2 = new MindCacheSync(store, mindcache2, {
4322
+ filePath: gitstore.path || "mindcache.md"
4323
+ });
4324
+ await sync2.save({ message: message || "MindCache sync" });
4325
+ } else if (server) {
4326
+ await fetch(server.url, {
4327
+ method: "POST",
4328
+ headers: {
4329
+ "Content-Type": "text/plain",
4330
+ ...server.authToken ? { Authorization: `Bearer ${server.authToken}` } : {}
4331
+ },
4332
+ body: mindcache2.toMarkdown()
4333
+ });
4334
+ }
4335
+ if (mountedRef.current) {
4336
+ setLastSyncAt(/* @__PURE__ */ new Date());
4337
+ setHasLocalChanges(false);
4338
+ setStatus("idle");
4339
+ }
4340
+ } catch (err) {
4341
+ if (mountedRef.current) {
4342
+ const error2 = err instanceof Error ? err : new Error(String(err));
4343
+ setError(error2);
4344
+ setStatus("error");
4345
+ }
4346
+ throw err;
4347
+ }
4348
+ }, [mindcache2, gitstore, server, getToken]);
4349
+ const sync = useCallback(async () => {
4350
+ setStatus("syncing");
4351
+ try {
4352
+ await load();
4353
+ if (hasLocalChanges) {
4354
+ await save();
4355
+ }
4356
+ } catch (err) {
4357
+ }
4358
+ }, [load, save, hasLocalChanges]);
4359
+ const markSaved = useCallback(() => {
4360
+ setHasLocalChanges(false);
4361
+ }, []);
4362
+ useEffect(() => {
4363
+ if (!mindcache2 || !gitstore || saveDebounceMs <= 0) {
4364
+ return;
4365
+ }
4366
+ const unsubscribe = mindcache2.subscribeToAll(() => {
4367
+ setHasLocalChanges(true);
4368
+ if (saveTimeoutRef.current) {
4369
+ clearTimeout(saveTimeoutRef.current);
4370
+ }
4371
+ saveTimeoutRef.current = setTimeout(() => {
4372
+ save("Auto-save").catch(console.error);
4373
+ }, saveDebounceMs);
4374
+ });
4375
+ return () => {
4376
+ unsubscribe();
4377
+ if (saveTimeoutRef.current) {
4378
+ clearTimeout(saveTimeoutRef.current);
4379
+ }
4380
+ };
4381
+ }, [mindcache2, gitstore, saveDebounceMs, save]);
4382
+ useEffect(() => {
4383
+ if (!mindcache2 || autoSyncInterval <= 0) {
4384
+ return;
4385
+ }
4386
+ syncIntervalRef.current = setInterval(() => {
4387
+ sync().catch(console.error);
4388
+ }, autoSyncInterval);
4389
+ return () => {
4390
+ if (syncIntervalRef.current) {
4391
+ clearInterval(syncIntervalRef.current);
4392
+ }
4393
+ };
4394
+ }, [mindcache2, autoSyncInterval, sync]);
4395
+ useEffect(() => {
4396
+ if (loadOnMount && mindcache2 && (gitstore || server)) {
4397
+ load().catch(console.error);
4398
+ }
4399
+ }, [mindcache2, gitstore, server, loadOnMount]);
4400
+ useEffect(() => {
4401
+ mountedRef.current = true;
4402
+ return () => {
4403
+ mountedRef.current = false;
4404
+ };
4405
+ }, []);
4406
+ return {
4407
+ status,
4408
+ error,
4409
+ lastSyncAt,
4410
+ hasLocalChanges,
4411
+ load,
4412
+ save,
4413
+ sync,
4414
+ markSaved
4415
+ };
4416
+ }
3085
4417
 
3086
4418
  // src/index.ts
3087
4419
  var mindcache = new MindCache();
3088
4420
 
3089
- export { CloudAdapter, DEFAULT_KEY_ATTRIBUTES, IndexedDBAdapter, MindCache, OAuthClient, SystemTagHelpers, createOAuthClient, mindcache, useMindCache };
4421
+ export { CloudAdapter, DEFAULT_KEY_ATTRIBUTES, IndexedDBAdapter, MindCache, MindCacheChat, MindCacheProvider, OAuthClient, SchemaParser, SystemTagHelpers, createOAuthClient, mindcache, useClientChat, useLocalFirstSync, useMindCache, useMindCacheContext };
3090
4422
  //# sourceMappingURL=index.mjs.map
3091
4423
  //# sourceMappingURL=index.mjs.map