graphlit-client 1.0.20260217003 → 1.0.20260217004

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/client.d.ts CHANGED
@@ -51,6 +51,7 @@ declare class Graphlit {
51
51
  private bedrockClient?;
52
52
  private deepseekClient?;
53
53
  private xaiClient?;
54
+ private readonly conversationQueues;
54
55
  constructor(organizationIdOrOptions?: string | GraphlitClientOptions, environmentId?: string, jwtSecret?: string, ownerId?: string, userId?: string, apiUri?: string);
55
56
  /**
56
57
  * Initialize the Apollo client without regenerating the token.
@@ -2525,6 +2526,12 @@ declare class Graphlit {
2525
2526
  */
2526
2527
  promptAgent(prompt: string, conversationId?: string, specification?: Types.EntityReferenceInput, tools?: Types.ToolDefinitionInput[], toolHandlers?: Record<string, ToolHandler>, options?: AgentOptions, mimeType?: string, data?: string, // base64 encoded
2527
2528
  contentFilter?: Types.ContentCriteriaInput, augmentedFilter?: Types.ContentCriteriaInput, correlationId?: string, persona?: Types.EntityReferenceInput): Promise<AgentResult>;
2529
+ /**
2530
+ * Serializes async work per conversation ID to prevent concurrent formatConversation /
2531
+ * completeConversation calls from racing each other. Each call chains after the previous
2532
+ * one for the same conversation, so messages are always processed in order.
2533
+ */
2534
+ private enqueueForConversation;
2528
2535
  /**
2529
2536
  * Execute an agent with streaming response
2530
2537
  * @param prompt - The user prompt
package/dist/client.js CHANGED
@@ -155,6 +155,9 @@ class Graphlit {
155
155
  bedrockClient;
156
156
  deepseekClient;
157
157
  xaiClient;
158
+ // Serializes streamAgent calls per conversation to prevent race conditions
159
+ // when a user sends a second message before the first response completes.
160
+ conversationQueues = new Map();
158
161
  constructor(organizationIdOrOptions, environmentId, jwtSecret, ownerId, userId, apiUri) {
159
162
  // Handle both old constructor signature and new options object
160
163
  let options;
@@ -4686,6 +4689,29 @@ class Graphlit {
4686
4689
  clearTimeout(timeoutId);
4687
4690
  }
4688
4691
  }
4692
+ /**
4693
+ * Serializes async work per conversation ID to prevent concurrent formatConversation /
4694
+ * completeConversation calls from racing each other. Each call chains after the previous
4695
+ * one for the same conversation, so messages are always processed in order.
4696
+ */
4697
+ enqueueForConversation(conversationId, work, abortSignal) {
4698
+ const previous = this.conversationQueues.get(conversationId) ?? Promise.resolve();
4699
+ // Swallow errors from the previous call so a failed message doesn't
4700
+ // permanently block the queue for this conversation.
4701
+ // Check the abort signal before starting work so ESC while queued is instant.
4702
+ const next = previous.catch(() => { }).then(() => {
4703
+ if (abortSignal?.aborted)
4704
+ throw new Error("Operation aborted");
4705
+ return work();
4706
+ });
4707
+ this.conversationQueues.set(conversationId, next);
4708
+ next.finally(() => {
4709
+ if (this.conversationQueues.get(conversationId) === next) {
4710
+ this.conversationQueues.delete(conversationId);
4711
+ }
4712
+ });
4713
+ return next;
4714
+ }
4689
4715
  /**
4690
4716
  * Execute an agent with streaming response
4691
4717
  * @param prompt - The user prompt
@@ -4744,107 +4770,130 @@ class Graphlit {
4744
4770
  throw new Error("Failed to create conversation");
4745
4771
  }
4746
4772
  }
4747
- // Check streaming support - fallback to promptAgent if not supported
4748
- if (fullSpec && !this.supportsStreaming(fullSpec, tools)) {
4749
- if (process.env.DEBUG_GRAPHLIT_SDK_STREAMING) {
4750
- console.log("\n⚠️ [streamAgent] Streaming not supported, falling back to promptAgent with same conversation");
4751
- }
4752
- // Fallback to promptAgent using the same conversation and parameters
4753
- const promptResult = await this.promptAgent(prompt, actualConversationId, // Preserve conversation
4754
- specification, tools, toolHandlers, {
4755
- maxToolRounds: maxRounds,
4756
- }, mimeType, data, contentFilter, augmentedFilter, correlationId, persona);
4757
- // Convert promptAgent result to streaming events
4773
+ // Serialize execution per-conversation: if the user sends a second message
4774
+ // before the first response completes, queue it so formatConversation /
4775
+ // completeConversation calls never interleave on the same conversation.
4776
+ if (this.conversationQueues.has(actualConversationId)) {
4758
4777
  onEvent({
4759
- type: "conversation_started",
4778
+ type: "conversation_queued",
4760
4779
  conversationId: actualConversationId,
4761
4780
  timestamp: new Date(),
4762
4781
  });
4763
- // Debug logging for fallback
4764
- if (process.env.DEBUG_GRAPHLIT_SDK_STREAMING) {
4765
- console.log(`📊 [streamAgent fallback] promptAgent result:`, {
4766
- hasMessage: !!promptResult.message,
4767
- messageLength: promptResult.message?.length,
4768
- toolCallsCount: promptResult.toolCalls?.length || 0,
4769
- toolResultsCount: promptResult.toolResults?.length || 0,
4770
- toolCalls: promptResult.toolCalls,
4771
- toolResults: promptResult.toolResults?.map((tr) => ({
4772
- name: tr.name,
4773
- hasResult: !!tr.result,
4774
- hasError: !!tr.error,
4775
- })),
4782
+ }
4783
+ await this.enqueueForConversation(actualConversationId, async () => {
4784
+ // Check streaming support - fallback to promptAgent if not supported
4785
+ if (fullSpec && !this.supportsStreaming(fullSpec, tools)) {
4786
+ if (process.env.DEBUG_GRAPHLIT_SDK_STREAMING) {
4787
+ console.log("\n⚠️ [streamAgent] Streaming not supported, falling back to promptAgent with same conversation");
4788
+ }
4789
+ // Fallback to promptAgent using the same conversation and parameters
4790
+ const promptResult = await this.promptAgent(prompt, actualConversationId, // Preserve conversation
4791
+ specification, tools, toolHandlers, {
4792
+ maxToolRounds: maxRounds,
4793
+ }, mimeType, data, contentFilter, augmentedFilter, correlationId, persona);
4794
+ // Convert promptAgent result to streaming events
4795
+ onEvent({
4796
+ type: "conversation_started",
4797
+ conversationId: actualConversationId,
4798
+ timestamp: new Date(),
4776
4799
  });
4777
- }
4778
- // Emit tool events if there were tool calls
4779
- if (promptResult.toolCalls && promptResult.toolCalls.length > 0) {
4780
- for (const toolCall of promptResult.toolCalls) {
4781
- onEvent({
4782
- type: "tool_update",
4783
- toolCall: toolCall,
4784
- status: "completed",
4800
+ // Debug logging for fallback
4801
+ if (process.env.DEBUG_GRAPHLIT_SDK_STREAMING) {
4802
+ console.log(`📊 [streamAgent fallback] promptAgent result:`, {
4803
+ hasMessage: !!promptResult.message,
4804
+ messageLength: promptResult.message?.length,
4805
+ toolCallsCount: promptResult.toolCalls?.length || 0,
4806
+ toolResultsCount: promptResult.toolResults?.length || 0,
4807
+ toolCalls: promptResult.toolCalls,
4808
+ toolResults: promptResult.toolResults?.map((tr) => ({
4809
+ name: tr.name,
4810
+ hasResult: !!tr.result,
4811
+ hasError: !!tr.error,
4812
+ })),
4785
4813
  });
4786
4814
  }
4815
+ // Emit tool events if there were tool calls
4816
+ if (promptResult.toolCalls && promptResult.toolCalls.length > 0) {
4817
+ for (const toolCall of promptResult.toolCalls) {
4818
+ onEvent({
4819
+ type: "tool_update",
4820
+ toolCall: toolCall,
4821
+ status: "completed",
4822
+ });
4823
+ }
4824
+ }
4825
+ // Emit the final message as a single update (simulating streaming)
4826
+ onEvent({
4827
+ type: "message_update",
4828
+ message: {
4829
+ __typename: "ConversationMessage",
4830
+ message: promptResult.message,
4831
+ role: Types.ConversationRoleTypes.Assistant,
4832
+ timestamp: new Date().toISOString(),
4833
+ toolCalls: promptResult.toolCalls || [],
4834
+ },
4835
+ isStreaming: false,
4836
+ });
4837
+ // Emit completion event
4838
+ onEvent({
4839
+ type: "conversation_completed",
4840
+ message: {
4841
+ __typename: "ConversationMessage",
4842
+ message: promptResult.message,
4843
+ role: Types.ConversationRoleTypes.Assistant,
4844
+ timestamp: new Date().toISOString(),
4845
+ toolCalls: promptResult.toolCalls || [],
4846
+ },
4847
+ });
4848
+ return; // Exit early after successful fallback
4787
4849
  }
4788
- // Emit the final message as a single update (simulating streaming)
4789
- onEvent({
4790
- type: "message_update",
4791
- message: {
4792
- __typename: "ConversationMessage",
4793
- message: promptResult.message,
4794
- role: Types.ConversationRoleTypes.Assistant,
4795
- timestamp: new Date().toISOString(),
4796
- toolCalls: promptResult.toolCalls || [],
4797
- },
4798
- isStreaming: false,
4799
- });
4800
- // Emit completion event
4801
- onEvent({
4802
- type: "conversation_completed",
4803
- message: {
4804
- __typename: "ConversationMessage",
4805
- message: promptResult.message,
4806
- role: Types.ConversationRoleTypes.Assistant,
4807
- timestamp: new Date().toISOString(),
4808
- toolCalls: promptResult.toolCalls || [],
4809
- },
4850
+ // Create UI event adapter with model information
4851
+ const modelName = fullSpec ? getModelName(fullSpec) : undefined;
4852
+ const modelEnum = fullSpec ? getModelEnum(fullSpec) : undefined;
4853
+ const serviceType = fullSpec ? getServiceType(fullSpec) : undefined;
4854
+ uiAdapter = new UIEventAdapter(onEvent, actualConversationId, {
4855
+ smoothingEnabled: options?.smoothingEnabled ?? true,
4856
+ chunkingStrategy: options?.chunkingStrategy ?? "word",
4857
+ smoothingDelay: options?.smoothingDelay ?? 30,
4858
+ model: modelEnum,
4859
+ modelName: modelName,
4860
+ modelService: serviceType,
4810
4861
  });
4811
- return; // Exit early after successful fallback
4812
- }
4813
- // Create UI event adapter with model information
4814
- const modelName = fullSpec ? getModelName(fullSpec) : undefined;
4815
- const modelEnum = fullSpec ? getModelEnum(fullSpec) : undefined;
4816
- const serviceType = fullSpec ? getServiceType(fullSpec) : undefined;
4817
- uiAdapter = new UIEventAdapter(onEvent, actualConversationId, {
4818
- smoothingEnabled: options?.smoothingEnabled ?? true,
4819
- chunkingStrategy: options?.chunkingStrategy ?? "word",
4820
- smoothingDelay: options?.smoothingDelay ?? 30,
4821
- model: modelEnum,
4822
- modelName: modelName,
4823
- modelService: serviceType,
4824
- });
4825
- // Start the streaming conversation
4826
- await this.executeStreamingAgent(prompt, actualConversationId, fullSpec, tools, toolHandlers, uiAdapter, maxRounds, abortSignal, mimeType, data, correlationId, persona);
4862
+ // Start the streaming conversation
4863
+ await this.executeStreamingAgent(prompt, actualConversationId, fullSpec, tools, toolHandlers, uiAdapter, maxRounds, abortSignal, mimeType, data, correlationId, persona);
4864
+ }, abortSignal);
4827
4865
  }
4828
4866
  catch (error) {
4829
- const errorMessage = error instanceof Error ? error.message : "Streaming failed";
4830
- if (uiAdapter) {
4831
- uiAdapter.handleEvent({
4832
- type: "error",
4833
- error: errorMessage,
4834
- });
4835
- }
4836
- else {
4837
- // Fallback error event
4867
+ const isAbortError = (error instanceof Error && error.message === "Operation aborted") ||
4868
+ (error instanceof DOMException && error.name === "AbortError");
4869
+ if (isAbortError) {
4838
4870
  onEvent({
4839
- type: "error",
4840
- error: {
4841
- message: errorMessage,
4842
- recoverable: false,
4843
- },
4871
+ type: "conversation_cancelled",
4844
4872
  conversationId: conversationId || "",
4845
4873
  timestamp: new Date(),
4846
4874
  });
4847
4875
  }
4876
+ else {
4877
+ const errorMessage = error instanceof Error ? error.message : "Streaming failed";
4878
+ if (uiAdapter) {
4879
+ uiAdapter.handleEvent({
4880
+ type: "error",
4881
+ error: errorMessage,
4882
+ });
4883
+ }
4884
+ else {
4885
+ // Fallback error event
4886
+ onEvent({
4887
+ type: "error",
4888
+ error: {
4889
+ message: errorMessage,
4890
+ recoverable: false,
4891
+ },
4892
+ conversationId: conversationId || "",
4893
+ timestamp: new Date(),
4894
+ });
4895
+ }
4896
+ }
4848
4897
  throw error;
4849
4898
  }
4850
4899
  finally {
@@ -35,6 +35,14 @@ export type AgentStreamEvent = {
35
35
  conversationId: string;
36
36
  timestamp: Date;
37
37
  model?: string;
38
+ } | {
39
+ type: "conversation_queued";
40
+ conversationId: string;
41
+ timestamp: Date;
42
+ } | {
43
+ type: "conversation_cancelled";
44
+ conversationId: string;
45
+ timestamp: Date;
38
46
  } | ContextWindowEvent | {
39
47
  type: "message_update";
40
48
  message: StreamingConversationMessage;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "graphlit-client",
3
- "version": "1.0.20260217003",
3
+ "version": "1.0.20260217004",
4
4
  "description": "Graphlit API Client for TypeScript",
5
5
  "type": "module",
6
6
  "main": "./dist/client.js",