@flutchai/flutch-sdk 0.2.20 → 0.3.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.cjs CHANGED
@@ -15,6 +15,7 @@ var crypto = require('crypto');
15
15
  var promClient = require('prom-client');
16
16
  var messages = require('@langchain/core/messages');
17
17
  var LangGraph = require('@langchain/langgraph');
18
+ var async_hooks = require('async_hooks');
18
19
  var dispatch = require('@langchain/core/callbacks/dispatch');
19
20
  var tools = require('@langchain/core/tools');
20
21
  var zod = require('zod');
@@ -23,6 +24,7 @@ var zodToJsonSchema = require('zod-to-json-schema');
23
24
  var manager = require('@langchain/core/callbacks/manager');
24
25
  var openai = require('@langchain/openai');
25
26
  var aws = require('@langchain/aws');
27
+ var core$1 = require('cohere-ai/core');
26
28
  var anthropic = require('@langchain/anthropic');
27
29
  var cohere = require('@langchain/cohere');
28
30
  var cohereAi = require('cohere-ai');
@@ -4817,6 +4819,36 @@ exports.EventProcessor = class EventProcessor {
4817
4819
  }
4818
4820
  return [];
4819
4821
  }
4822
+ /**
4823
+ * Convert LangChain unified `tool_call_chunks` (Bedrock / OpenAI streaming
4824
+ * format) into Anthropic-style blocks so the rest of the pipeline can stay
4825
+ * uniform. The first chunk for a tool carries `name` (and usually `id`);
4826
+ * subsequent chunks carry partial `args` strings that need to accumulate.
4827
+ *
4828
+ * NOTE: assumes tool calls arrive sequentially. Parallel tool calls with
4829
+ * interleaved `args` chunks across different `index`es will misroute args
4830
+ * to the most recently opened tool_use block.
4831
+ */
4832
+ toolCallChunksToBlocks(toolCallChunks) {
4833
+ const blocks = [];
4834
+ for (const tcc of toolCallChunks) {
4835
+ if (tcc.name) {
4836
+ blocks.push({
4837
+ type: "tool_use",
4838
+ id: tcc.id,
4839
+ name: tcc.name,
4840
+ input: ""
4841
+ });
4842
+ }
4843
+ if (typeof tcc.args === "string" && tcc.args.length > 0) {
4844
+ blocks.push({
4845
+ type: "input_json_delta",
4846
+ input: tcc.args
4847
+ });
4848
+ }
4849
+ }
4850
+ return blocks;
4851
+ }
4820
4852
  /**
4821
4853
  * Extract attachments from various input formats
4822
4854
  * Handles both array format (IAttachment[]) and object format (Record<string, IGraphAttachment>)
@@ -4980,10 +5012,19 @@ exports.EventProcessor = class EventProcessor {
4980
5012
  }
4981
5013
  return;
4982
5014
  }
4983
- if (event.event === "on_chat_model_stream" && event.data?.chunk?.content) {
5015
+ if (event.event === "on_chat_model_stream" && event.data?.chunk) {
4984
5016
  const channel = event.metadata?.stream_channel ?? "text" /* TEXT */;
4985
- const blocks = this.normalizeContentBlocks(event.data.chunk.content);
4986
- this.processContentStream(acc, channel, blocks, onPartial);
5017
+ const chunk = event.data.chunk;
5018
+ const blocks = [];
5019
+ if (chunk.content) {
5020
+ blocks.push(...this.normalizeContentBlocks(chunk.content));
5021
+ }
5022
+ if (Array.isArray(chunk.tool_call_chunks) && chunk.tool_call_chunks.length > 0) {
5023
+ blocks.push(...this.toolCallChunksToBlocks(chunk.tool_call_chunks));
5024
+ }
5025
+ if (blocks.length > 0) {
5026
+ this.processContentStream(acc, channel, blocks, onPartial);
5027
+ }
4987
5028
  return;
4988
5029
  }
4989
5030
  if (event.event === "on_tool_start") {
@@ -4996,6 +5037,44 @@ exports.EventProcessor = class EventProcessor {
4996
5037
  if (idx !== -1) {
4997
5038
  const block = state.pendingToolBlocks.splice(idx, 1)[0];
4998
5039
  state.toolBlocksByRunId.set(event.run_id, block);
5040
+ } else {
5041
+ const toolInput = event.data?.input;
5042
+ let inputString;
5043
+ try {
5044
+ inputString = typeof toolInput === "string" ? toolInput : JSON.stringify(toolInput ?? {});
5045
+ } catch {
5046
+ inputString = "";
5047
+ }
5048
+ const synthesizedBlock = {
5049
+ index: state.contentChain.length + (state.currentBlock ? 1 : 0),
5050
+ type: "tool_use",
5051
+ name: event.name,
5052
+ id: event.run_id,
5053
+ input: inputString,
5054
+ output: ""
5055
+ };
5056
+ if (state.currentBlock) {
5057
+ state.contentChain.push(state.currentBlock);
5058
+ state.currentBlock = null;
5059
+ }
5060
+ state.contentChain.push(synthesizedBlock);
5061
+ state.toolBlocksByRunId.set(event.run_id, synthesizedBlock);
5062
+ this.sendDelta(
5063
+ channel,
5064
+ { type: "step_started", step: synthesizedBlock },
5065
+ onPartial
5066
+ );
5067
+ if (inputString.length > 0) {
5068
+ this.sendDelta(
5069
+ channel,
5070
+ {
5071
+ type: "tool_input_chunk",
5072
+ stepId: synthesizedBlock.id,
5073
+ chunk: inputString
5074
+ },
5075
+ onPartial
5076
+ );
5077
+ }
4999
5078
  }
5000
5079
  }
5001
5080
  this.logger.log("\u{1F527} Tool execution started", {
@@ -5225,6 +5304,71 @@ exports.EventProcessor = class EventProcessor {
5225
5304
  exports.EventProcessor = __decorateClass([
5226
5305
  common.Injectable()
5227
5306
  ], exports.EventProcessor);
5307
+ var als = new async_hooks.AsyncLocalStorage();
5308
+ function withFlutchContext(ctx, fn) {
5309
+ return als.run(ctx, fn);
5310
+ }
5311
+ function getFlutchContext() {
5312
+ return als.getStore();
5313
+ }
5314
+ var HEADER_MAP = {
5315
+ messageId: "x-flutch-message-id",
5316
+ threadId: "x-flutch-thread-id",
5317
+ agentId: "x-flutch-agent-id",
5318
+ userId: "x-flutch-user-id",
5319
+ nodeName: "x-flutch-node"
5320
+ };
5321
+ var flutchFetch = (input, init) => {
5322
+ const ctx = als.getStore();
5323
+ if (!ctx) {
5324
+ return fetch(input, init);
5325
+ }
5326
+ const headers = new Headers(init?.headers);
5327
+ for (const key of Object.keys(HEADER_MAP)) {
5328
+ const value = ctx[key];
5329
+ if (value) {
5330
+ headers.set(HEADER_MAP[key], String(value));
5331
+ }
5332
+ }
5333
+ return fetch(input, { ...init, headers });
5334
+ };
5335
+ function flutchHeaders() {
5336
+ const ctx = als.getStore();
5337
+ if (!ctx) return {};
5338
+ const out = {};
5339
+ for (const key of Object.keys(HEADER_MAP)) {
5340
+ const value = ctx[key];
5341
+ if (value) {
5342
+ out[HEADER_MAP[key]] = String(value);
5343
+ }
5344
+ }
5345
+ return out;
5346
+ }
5347
+ function flutchMistralHook(req) {
5348
+ const extras = flutchHeaders();
5349
+ if (Object.keys(extras).length === 0) {
5350
+ return req;
5351
+ }
5352
+ const headers = new Headers(req.headers);
5353
+ for (const [k, v] of Object.entries(extras)) {
5354
+ headers.set(k, v);
5355
+ }
5356
+ return new Request(req, { headers });
5357
+ }
5358
+ function wrapCohereFetcher(inner) {
5359
+ return ((args) => {
5360
+ const extras = flutchHeaders();
5361
+ if (Object.keys(extras).length === 0) {
5362
+ return inner(args);
5363
+ }
5364
+ return inner({
5365
+ ...args,
5366
+ headers: { ...args.headers ?? {}, ...extras }
5367
+ });
5368
+ });
5369
+ }
5370
+
5371
+ // src/engines/langgraph/langgraph-engine.ts
5228
5372
  process.setMaxListeners(0);
5229
5373
  exports.LangGraphEngine = class LangGraphEngine {
5230
5374
  constructor(eventProcessor, configService) {
@@ -5276,29 +5420,42 @@ exports.LangGraphEngine = class LangGraphEngine {
5276
5420
  * Method to invoke LangGraph
5277
5421
  */
5278
5422
  async invokeGraph(graph, preparedPayload, signal) {
5279
- this.logger.debug("invokeGraph preparedPayload", preparedPayload);
5280
- if (signal) {
5281
- preparedPayload.signal = signal;
5282
- this.logger.debug("[ENGINE] Signal assigned to preparedPayload.signal");
5283
- }
5284
- const input = await this.deserializeInput(preparedPayload.input || {});
5285
- try {
5286
- const result = await graph.invoke(input, {
5287
- ...preparedPayload.config,
5288
- signal: preparedPayload.signal
5289
- });
5290
- return this.processGraphResult(result);
5291
- } finally {
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
- );
5423
+ return withFlutchContext(
5424
+ this.extractFlutchContext(preparedPayload),
5425
+ async () => {
5426
+ this.logger.debug("invokeGraph preparedPayload", preparedPayload);
5427
+ if (signal) {
5428
+ preparedPayload.signal = signal;
5429
+ this.logger.debug(
5430
+ "[ENGINE] Signal assigned to preparedPayload.signal"
5431
+ );
5432
+ }
5433
+ const input = await this.deserializeInput(preparedPayload.input || {});
5434
+ try {
5435
+ const result = await graph.invoke(input, {
5436
+ ...preparedPayload.config,
5437
+ signal: preparedPayload.signal
5438
+ });
5439
+ return this.processGraphResult(result);
5440
+ } finally {
5441
+ const threadId = this.extractThreadId(preparedPayload);
5442
+ if (threadId) {
5443
+ clearAttachmentDataStore(threadId);
5444
+ this.logger.debug(
5445
+ `[ENGINE] Cleared attachment data store for thread: ${threadId}`
5446
+ );
5447
+ }
5448
+ }
5298
5449
  }
5299
- }
5450
+ );
5300
5451
  }
5301
5452
  async streamGraph(graph, preparedPayload, onPartial, signal) {
5453
+ return withFlutchContext(
5454
+ this.extractFlutchContext(preparedPayload),
5455
+ () => this.streamGraphInner(graph, preparedPayload, onPartial, signal)
5456
+ );
5457
+ }
5458
+ async streamGraphInner(graph, preparedPayload, onPartial, signal) {
5302
5459
  const acc = this.eventProcessor.createAccumulator();
5303
5460
  let streamError = null;
5304
5461
  this.logger.debug({
@@ -5528,6 +5685,22 @@ exports.LangGraphEngine = class LangGraphEngine {
5528
5685
  extractThreadId(preparedPayload) {
5529
5686
  return preparedPayload.configurable?.thread_id || preparedPayload.configurable?.context?.threadId || preparedPayload.config?.configurable?.thread_id || preparedPayload.config?.configurable?.context?.threadId || void 0;
5530
5687
  }
5688
+ /**
5689
+ * Extract Flutch billing/attribution context from the prepared payload.
5690
+ *
5691
+ * Read by `flutchFetch` (in node-sdk/src/models/flutch-context.ts) to
5692
+ * attach X-Flutch-* headers to every router-bound LLM call made during
5693
+ * this graph run.
5694
+ */
5695
+ extractFlutchContext(preparedPayload) {
5696
+ const ctx = preparedPayload?.config?.configurable?.context || preparedPayload?.configurable?.context || {};
5697
+ return {
5698
+ messageId: ctx.messageId,
5699
+ threadId: ctx.threadId || preparedPayload?.config?.configurable?.thread_id,
5700
+ agentId: ctx.agentId,
5701
+ userId: ctx.userId
5702
+ };
5703
+ }
5531
5704
  /**
5532
5705
  * Process graph execution result
5533
5706
  */
@@ -6683,7 +6856,10 @@ var ModelInitializer = class _ModelInitializer {
6683
6856
  );
6684
6857
  const routerURL = resolveRouterURL(baseURL);
6685
6858
  if (routerURL) {
6686
- config.configuration = { baseURL: `${routerURL}/v1` };
6859
+ config.configuration = {
6860
+ baseURL: `${routerURL}/v1`,
6861
+ fetch: flutchFetch
6862
+ };
6687
6863
  }
6688
6864
  return new openai.ChatOpenAI(config);
6689
6865
  },
@@ -6693,15 +6869,19 @@ var ModelInitializer = class _ModelInitializer {
6693
6869
  defaultMaxTokens,
6694
6870
  apiToken,
6695
6871
  baseURL
6696
- }) => new anthropic.ChatAnthropic({
6697
- modelName,
6698
- temperature: defaultTemperature,
6699
- maxTokens: defaultMaxTokens,
6700
- anthropicApiKey: apiToken || this.resolveApiKey("anthropic" /* ANTHROPIC */),
6701
- ...resolveRouterURL(baseURL) && {
6702
- anthropicApiUrl: resolveRouterURL(baseURL)
6703
- }
6704
- }),
6872
+ }) => {
6873
+ const routerURL = resolveRouterURL(baseURL);
6874
+ return new anthropic.ChatAnthropic({
6875
+ modelName,
6876
+ temperature: defaultTemperature,
6877
+ maxTokens: defaultMaxTokens,
6878
+ anthropicApiKey: apiToken || this.resolveApiKey("anthropic" /* ANTHROPIC */),
6879
+ ...routerURL && {
6880
+ anthropicApiUrl: routerURL,
6881
+ clientOptions: { fetch: flutchFetch }
6882
+ }
6883
+ });
6884
+ },
6705
6885
  ["cohere" /* COHERE */]: ({
6706
6886
  modelName,
6707
6887
  defaultTemperature,
@@ -6714,7 +6894,13 @@ var ModelInitializer = class _ModelInitializer {
6714
6894
  return routerURL ? new cohere.ChatCohere({
6715
6895
  model: modelName,
6716
6896
  temperature: defaultTemperature,
6717
- client: new cohereAi.CohereClient({ token, baseUrl: routerURL })
6897
+ client: new cohereAi.CohereClient({
6898
+ token,
6899
+ baseUrl: routerURL,
6900
+ // Inject X-Flutch-* headers from the ALS context on every
6901
+ // outbound request via the wrapped default fetcher.
6902
+ fetcher: wrapCohereFetcher(core$1.fetcher)
6903
+ })
6718
6904
  }) : new cohere.ChatCohere({
6719
6905
  model: modelName,
6720
6906
  temperature: defaultTemperature,
@@ -6734,7 +6920,13 @@ var ModelInitializer = class _ModelInitializer {
6734
6920
  temperature: defaultTemperature,
6735
6921
  maxTokens: defaultMaxTokens,
6736
6922
  apiKey: apiToken || this.resolveApiKey("mistral" /* MISTRAL */),
6737
- ...routerURL && { serverURL: `${routerURL}/v1` }
6923
+ ...routerURL && {
6924
+ serverURL: `${routerURL}/v1`,
6925
+ // Mistral SDK doesn't accept a custom fetch directly — its
6926
+ // beforeRequestHooks let us mutate the outgoing Request to add
6927
+ // X-Flutch-* headers from the ALS context.
6928
+ beforeRequestHooks: [flutchMistralHook]
6929
+ }
6738
6930
  });
6739
6931
  },
6740
6932
  ["voyageai" /* VOYAGEAI */]: () => {
@@ -6789,7 +6981,7 @@ var ModelInitializer = class _ModelInitializer {
6789
6981
  model: modelName,
6790
6982
  apiKey: apiToken || this.resolveApiKey("openai" /* OPENAI */),
6791
6983
  ...routerURL && {
6792
- configuration: { baseURL: `${routerURL}/v1` }
6984
+ configuration: { baseURL: `${routerURL}/v1`, fetch: flutchFetch }
6793
6985
  }
6794
6986
  });
6795
6987
  },
@@ -7783,11 +7975,15 @@ exports.encryptTokens = encryptTokens;
7783
7975
  exports.executeToolWithAttachments = executeToolWithAttachments;
7784
7976
  exports.findCallbackMethod = findCallbackMethod;
7785
7977
  exports.findEndpointMethod = findEndpointMethod;
7978
+ exports.flutchFetch = flutchFetch;
7979
+ exports.flutchHeaders = flutchHeaders;
7980
+ exports.flutchMistralHook = flutchMistralHook;
7786
7981
  exports.generateAttachmentSummary = generateAttachmentSummary;
7787
7982
  exports.generateModelCacheKey = generateModelCacheKey;
7788
7983
  exports.getAttachmentData = getAttachmentData;
7789
7984
  exports.getCallbackMetadata = getCallbackMetadata;
7790
7985
  exports.getEndpointMetadata = getEndpointMetadata;
7986
+ exports.getFlutchContext = getFlutchContext;
7791
7987
  exports.getOAuthProvider = getOAuthProvider;
7792
7988
  exports.getOAuthProviderNames = getOAuthProviderNames;
7793
7989
  exports.getUIEndpointClassMetadata = getUIEndpointClassMetadata;
@@ -7805,5 +8001,7 @@ exports.resolveRouterURL = resolveRouterURL;
7805
8001
  exports.sanitizeTraceData = sanitizeTraceData;
7806
8002
  exports.storeAttachmentData = storeAttachmentData;
7807
8003
  exports.traceApiCall = traceApiCall;
8004
+ exports.withFlutchContext = withFlutchContext;
8005
+ exports.wrapCohereFetcher = wrapCohereFetcher;
7808
8006
  //# sourceMappingURL=index.cjs.map
7809
8007
  //# sourceMappingURL=index.cjs.map