@flutchai/flutch-sdk 0.2.4 → 0.2.6

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.cjs CHANGED
@@ -4422,6 +4422,48 @@ function generateTextSummary(data, toolCallId) {
4422
4422
 
4423
4423
  // src/graph/attachment-tool-node.ts
4424
4424
  var DEFAULT_ATTACHMENT_THRESHOLD = Number(process.env.ATTACHMENT_THRESHOLD) || 4e3;
4425
+ var attachmentDataStore = /* @__PURE__ */ new Map();
4426
+ var cleanupTimers = /* @__PURE__ */ new Map();
4427
+ var AUTO_CLEANUP_MS = 10 * 60 * 1e3;
4428
+ function storeAttachmentData(key, data, threadId) {
4429
+ const scope = threadId || "__global__";
4430
+ let threadStore = attachmentDataStore.get(scope);
4431
+ if (!threadStore) {
4432
+ threadStore = /* @__PURE__ */ new Map();
4433
+ attachmentDataStore.set(scope, threadStore);
4434
+ }
4435
+ threadStore.set(key, data);
4436
+ const existingTimer = cleanupTimers.get(scope);
4437
+ if (existingTimer) {
4438
+ clearTimeout(existingTimer);
4439
+ }
4440
+ const timer = setTimeout(() => {
4441
+ attachmentDataStore.delete(scope);
4442
+ cleanupTimers.delete(scope);
4443
+ }, AUTO_CLEANUP_MS);
4444
+ if (timer.unref) timer.unref();
4445
+ cleanupTimers.set(scope, timer);
4446
+ }
4447
+ function getAttachmentData(key, threadId) {
4448
+ const scope = threadId || "__global__";
4449
+ return attachmentDataStore.get(scope)?.get(key);
4450
+ }
4451
+ function clearAttachmentDataStore(threadId) {
4452
+ if (threadId) {
4453
+ attachmentDataStore.delete(threadId);
4454
+ const timer = cleanupTimers.get(threadId);
4455
+ if (timer) {
4456
+ clearTimeout(timer);
4457
+ cleanupTimers.delete(threadId);
4458
+ }
4459
+ } else {
4460
+ attachmentDataStore.clear();
4461
+ for (const timer of cleanupTimers.values()) {
4462
+ clearTimeout(timer);
4463
+ }
4464
+ cleanupTimers.clear();
4465
+ }
4466
+ }
4425
4467
  async function executeToolWithAttachments(params) {
4426
4468
  const {
4427
4469
  toolCall,
@@ -4433,17 +4475,23 @@ async function executeToolWithAttachments(params) {
4433
4475
  logger: logger2,
4434
4476
  threshold = DEFAULT_ATTACHMENT_THRESHOLD,
4435
4477
  injectIntoArg = "data",
4436
- sourceAttachmentId
4478
+ sourceAttachmentId,
4479
+ threadId
4437
4480
  } = params;
4438
4481
  const argsWithInjection = { ...enrichedArgs };
4439
4482
  try {
4440
4483
  if (shouldInjectData(argsWithInjection, attachments, injectIntoArg)) {
4441
4484
  const attachment = sourceAttachmentId ? attachments[sourceAttachmentId] : getLatestAttachment(attachments);
4442
4485
  if (attachment) {
4443
- argsWithInjection[injectIntoArg] = typeof attachment.data === "string" ? attachment.data : JSON.stringify(attachment.data);
4444
- logger2?.debug(
4445
- `[Attachment] Auto-injected data from attachment "${attachment.toolCallId}" into ${toolCall.name}.${injectIntoArg}`
4446
- );
4486
+ const attachmentKey = sourceAttachmentId || attachment.toolCallId;
4487
+ const storedData = getAttachmentData(attachmentKey, threadId);
4488
+ const data = storedData !== void 0 ? storedData : attachment.data;
4489
+ if (data != null) {
4490
+ argsWithInjection[injectIntoArg] = typeof data === "string" ? data : JSON.stringify(data);
4491
+ logger2?.debug(
4492
+ `[Attachment] Auto-injected data from attachment "${attachment.toolCallId}" into ${toolCall.name}.${injectIntoArg} (source: ${storedData !== void 0 ? "memory" : "state"})`
4493
+ );
4494
+ }
4447
4495
  }
4448
4496
  }
4449
4497
  } catch (e) {
@@ -4463,8 +4511,14 @@ async function executeToolWithAttachments(params) {
4463
4511
  toolCall.name,
4464
4512
  toolCall.id
4465
4513
  );
4514
+ storeAttachmentData(toolCall.id, attachment.data, threadId);
4515
+ const stateAttachment = {
4516
+ ...attachment,
4517
+ data: null
4518
+ // Data stored in memory, not in graph state
4519
+ };
4466
4520
  logger2?.debug(
4467
- `[Attachment] Stored large result (${content.length} chars) as attachment "${toolCall.id}"`
4521
+ `[Attachment] Stored large result (${content.length} chars) as attachment "${toolCall.id}" (data in memory, metadata in state)`
4468
4522
  );
4469
4523
  const toolMessage2 = new messages.ToolMessage({
4470
4524
  content: attachment.summary,
@@ -4473,7 +4527,7 @@ async function executeToolWithAttachments(params) {
4473
4527
  });
4474
4528
  return {
4475
4529
  toolMessage: toolMessage2,
4476
- attachment: { key: toolCall.id, value: attachment }
4530
+ attachment: { key: toolCall.id, value: stateAttachment }
4477
4531
  };
4478
4532
  }
4479
4533
  } catch (e) {
@@ -4505,7 +4559,8 @@ function getLatestAttachment(attachments) {
4505
4559
  }
4506
4560
  var logger = new common.Logger("ApiCallTracer");
4507
4561
  var DEFAULT_TRACER_OPTIONS = {
4508
- maxStringLength: 5e3,
4562
+ maxStringLength: 1e5,
4563
+ // 100KB - enough for most tool outputs, prevents overflow
4509
4564
  maxDepth: 15
4510
4565
  };
4511
4566
  var cachedDispatch;
@@ -4571,6 +4626,9 @@ function sanitizeTraceData(value, depth = 0, seen = /* @__PURE__ */ new WeakSet(
4571
4626
  return null;
4572
4627
  }
4573
4628
  if (typeof value === "string") {
4629
+ if (value.length > opts.maxStringLength) {
4630
+ return `${value.slice(0, opts.maxStringLength)}\u2026 [truncated: ${value.length - opts.maxStringLength} chars]`;
4631
+ }
4574
4632
  return value;
4575
4633
  }
4576
4634
  if (typeof value === "number" || typeof value === "boolean") {
@@ -4735,6 +4793,19 @@ exports.EventProcessor = class EventProcessor {
4735
4793
  }
4736
4794
  return [];
4737
4795
  }
4796
+ /**
4797
+ * Extract attachments from various input formats
4798
+ * Handles both array format (IAttachment[]) and object format (Record<string, IGraphAttachment>)
4799
+ */
4800
+ extractAttachments(attachments) {
4801
+ if (!attachments) {
4802
+ return [];
4803
+ }
4804
+ const items = Array.isArray(attachments) ? attachments : typeof attachments === "object" ? Object.values(attachments) : [];
4805
+ return items.filter(
4806
+ (item) => item != null && "type" in item && "value" in item
4807
+ );
4808
+ }
4738
4809
  /**
4739
4810
  * Send delta to UI (unified format)
4740
4811
  */
@@ -4872,6 +4943,14 @@ exports.EventProcessor = class EventProcessor {
4872
4943
  const blocks = this.normalizeContentBlocks(event.data.content);
4873
4944
  this.processContentStream(acc, channel, blocks, onPartial);
4874
4945
  }
4946
+ if (event.name === "send_attachments" && event.data.attachments) {
4947
+ const attachments = event.data.attachments || [];
4948
+ acc.attachments = [...acc.attachments, ...attachments];
4949
+ this.logger.debug("[ATTACHMENTS] Extracted from send_attachments event", {
4950
+ extractedCount: attachments.length,
4951
+ totalAccCount: acc.attachments.length
4952
+ });
4953
+ }
4875
4954
  return;
4876
4955
  }
4877
4956
  if (event.event === "on_chat_model_stream" && event.data?.chunk?.content) {
@@ -4911,7 +4990,21 @@ exports.EventProcessor = class EventProcessor {
4911
4990
  }
4912
4991
  if (toolBlock && toolBlock.type === "tool_use") {
4913
4992
  const output = event.data?.output;
4914
- const outputString = typeof output === "string" ? output : JSON.stringify(output, null, 2);
4993
+ let outputString;
4994
+ try {
4995
+ outputString = typeof output === "string" ? output : JSON.stringify(output, null, 2);
4996
+ } catch {
4997
+ outputString = typeof output?.content === "string" ? output.content : "[Output too large to display]";
4998
+ }
4999
+ const MAX_TOOL_OUTPUT_LENGTH = 5e4;
5000
+ if (outputString.length > MAX_TOOL_OUTPUT_LENGTH) {
5001
+ let cutAt = outputString.lastIndexOf("\n", MAX_TOOL_OUTPUT_LENGTH);
5002
+ if (cutAt < MAX_TOOL_OUTPUT_LENGTH * 0.8) {
5003
+ cutAt = MAX_TOOL_OUTPUT_LENGTH;
5004
+ }
5005
+ outputString = outputString.slice(0, cutAt) + `
5006
+ ... [truncated: ${outputString.length - cutAt} chars]`;
5007
+ }
4915
5008
  toolBlock.output = outputString;
4916
5009
  this.sendDelta(
4917
5010
  channel,
@@ -4955,32 +5048,6 @@ exports.EventProcessor = class EventProcessor {
4955
5048
  });
4956
5049
  return;
4957
5050
  }
4958
- if (event.event === "on_chain_end") {
4959
- const channel = event.metadata?.stream_channel ?? "text" /* TEXT */;
4960
- if (channel === "text" /* TEXT */) {
4961
- const output = event.data.output;
4962
- if (output?.answer) {
4963
- acc.attachments = [
4964
- ...acc.attachments,
4965
- ...output.answer.attachments || []
4966
- ];
4967
- acc.metadata = { ...acc.metadata, ...output.answer.metadata || {} };
4968
- } else if (output?.generation) {
4969
- acc.attachments = [
4970
- ...acc.attachments,
4971
- ...output.generation.attachments || []
4972
- ];
4973
- acc.metadata = {
4974
- ...acc.metadata,
4975
- ...output.generation.metadata || {}
4976
- };
4977
- } else if (output?.attachments || output?.metadata) {
4978
- acc.attachments = [...acc.attachments, ...output.attachments || []];
4979
- acc.metadata = { ...acc.metadata, ...output.metadata || {} };
4980
- }
4981
- }
4982
- return;
4983
- }
4984
5051
  }
4985
5052
  /**
4986
5053
  * Build final result from accumulator
@@ -5024,7 +5091,9 @@ exports.EventProcessor = class EventProcessor {
5024
5091
  textChains: allChains.filter((c) => c.channel === "text").length,
5025
5092
  processingChains: allChains.filter((c) => c.channel === "processing").length,
5026
5093
  totalSteps: allChains.reduce((sum, c) => sum + c.steps.length, 0),
5027
- textLength: text.length
5094
+ textLength: text.length,
5095
+ attachmentsCount: acc.attachments?.length || 0,
5096
+ attachments: acc.attachments
5028
5097
  });
5029
5098
  return {
5030
5099
  content: {
@@ -5165,11 +5234,21 @@ exports.LangGraphEngine = class LangGraphEngine {
5165
5234
  this.logger.debug("[ENGINE] Signal assigned to preparedPayload.signal");
5166
5235
  }
5167
5236
  const input = await this.deserializeInput(preparedPayload.input || {});
5168
- const result = await graph.invoke(input, {
5169
- ...preparedPayload.config,
5170
- signal: preparedPayload.signal
5171
- });
5172
- return this.processGraphResult(result);
5237
+ try {
5238
+ const result = await graph.invoke(input, {
5239
+ ...preparedPayload.config,
5240
+ signal: preparedPayload.signal
5241
+ });
5242
+ return this.processGraphResult(result);
5243
+ } finally {
5244
+ const threadId = this.extractThreadId(preparedPayload);
5245
+ if (threadId) {
5246
+ clearAttachmentDataStore(threadId);
5247
+ this.logger.debug(
5248
+ `[ENGINE] Cleared attachment data store for thread: ${threadId}`
5249
+ );
5250
+ }
5251
+ }
5173
5252
  }
5174
5253
  async streamGraph(graph, preparedPayload, onPartial, signal) {
5175
5254
  const acc = this.eventProcessor.createAccumulator();
@@ -5210,6 +5289,13 @@ exports.LangGraphEngine = class LangGraphEngine {
5210
5289
  this.logger.error(`[STREAM-ERROR] Stack trace: ${streamError.stack}`);
5211
5290
  } finally {
5212
5291
  await this.sendTraceFromAccumulator(acc, preparedPayload, streamError);
5292
+ const threadId = this.extractThreadId(preparedPayload);
5293
+ if (threadId) {
5294
+ clearAttachmentDataStore(threadId);
5295
+ this.logger.debug(
5296
+ `[ENGINE] Cleared attachment data store for thread: ${threadId}`
5297
+ );
5298
+ }
5213
5299
  }
5214
5300
  const { content, trace } = this.eventProcessor.getResult(acc);
5215
5301
  this.logger.debug("[STREAM-RESULT] Got result from EventProcessor", {
@@ -5388,6 +5474,12 @@ exports.LangGraphEngine = class LangGraphEngine {
5388
5474
  });
5389
5475
  }
5390
5476
  }
5477
+ /**
5478
+ * Extract threadId from payload (used for attachment store cleanup)
5479
+ */
5480
+ extractThreadId(preparedPayload) {
5481
+ return preparedPayload.configurable?.thread_id || preparedPayload.configurable?.context?.threadId || preparedPayload.config?.configurable?.thread_id || preparedPayload.config?.configurable?.context?.threadId || void 0;
5482
+ }
5391
5483
  /**
5392
5484
  * Process graph execution result
5393
5485
  */
@@ -5413,6 +5505,9 @@ async function createStaticMessage(content, config) {
5413
5505
  await dispatch.dispatchCustomEvent("send_static_message", { content }, config);
5414
5506
  return message;
5415
5507
  }
5508
+ async function dispatchAttachments(attachments, config) {
5509
+ await dispatch.dispatchCustomEvent("send_attachments", { attachments }, config);
5510
+ }
5416
5511
 
5417
5512
  // src/core/universal-graph.module.ts
5418
5513
  init_builder_registry_service();
@@ -7550,14 +7645,17 @@ exports.WithEndpoints = WithEndpoints;
7550
7645
  exports.WithUIEndpoints = WithUIEndpoints;
7551
7646
  exports._internals = _internals;
7552
7647
  exports.bootstrap = bootstrap;
7648
+ exports.clearAttachmentDataStore = clearAttachmentDataStore;
7553
7649
  exports.createEndpointDescriptors = createEndpointDescriptors;
7554
7650
  exports.createGraphAttachment = createGraphAttachment;
7555
7651
  exports.createMongoClientAdapter = createMongoClientAdapter;
7556
7652
  exports.createStaticMessage = createStaticMessage;
7653
+ exports.dispatchAttachments = dispatchAttachments;
7557
7654
  exports.executeToolWithAttachments = executeToolWithAttachments;
7558
7655
  exports.findCallbackMethod = findCallbackMethod;
7559
7656
  exports.findEndpointMethod = findEndpointMethod;
7560
7657
  exports.generateAttachmentSummary = generateAttachmentSummary;
7658
+ exports.getAttachmentData = getAttachmentData;
7561
7659
  exports.getCallbackMetadata = getCallbackMetadata;
7562
7660
  exports.getEndpointMetadata = getEndpointMetadata;
7563
7661
  exports.getUIEndpointClassMetadata = getUIEndpointClassMetadata;
@@ -7567,6 +7665,7 @@ exports.hasUIEndpoints = hasUIEndpoints;
7567
7665
  exports.registerFinanceExampleCallback = registerFinanceExampleCallback;
7568
7666
  exports.registerUIEndpointsFromClass = registerUIEndpointsFromClass;
7569
7667
  exports.sanitizeTraceData = sanitizeTraceData;
7668
+ exports.storeAttachmentData = storeAttachmentData;
7570
7669
  exports.traceApiCall = traceApiCall;
7571
7670
  //# sourceMappingURL=index.cjs.map
7572
7671
  //# sourceMappingURL=index.cjs.map