graphlit-client 1.0.20260406002 → 1.0.20260408001

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
@@ -3057,6 +3057,10 @@ declare class Graphlit {
3057
3057
  * Fallback to non-streaming when streaming is not available
3058
3058
  */
3059
3059
  private fallbackToNonStreaming;
3060
+ /**
3061
+ * Stream with OpenAI client
3062
+ */
3063
+ private streamWithOpenAIResponses;
3060
3064
  /**
3061
3065
  * Stream with OpenAI client
3062
3066
  */
package/dist/client.js CHANGED
@@ -6,13 +6,14 @@ import { RetryLink } from "@apollo/client/link/retry/index.js";
6
6
  import { attachPartialErrors } from "./partial-errors.js";
7
7
  import * as Types from "./generated/graphql-types.js";
8
8
  import * as Documents from "./generated/graphql-documents.js";
9
- import { getServiceType, getModelName, getModelEnum } from "./model-mapping.js";
9
+ import { getServiceType, getModelName, getModelEnum, isOpenAIResponsesEligibleModel, } from "./model-mapping.js";
10
10
  import { StuckDetector } from "./helpers/stuck-detector.js";
11
11
  import { TurnEvaluator } from "./helpers/turn-evaluator.js";
12
12
  import { TokenBudgetTracker, truncateToolResult, windowToolRounds, estimateTokens, DEFAULT_CONTEXT_STRATEGY, } from "./helpers/context-management.js";
13
13
  import { ProviderError } from "./types/internal.js";
14
14
  import { UIEventAdapter } from "./streaming/ui-event-adapter.js";
15
- import { formatMessagesForOpenAI, formatMessagesForAnthropic, formatMessagesForGoogle, formatMessagesForMistral, formatMessagesForBedrock, } from "./streaming/llm-formatters.js";
15
+ import { formatMessagesForOpenAI, formatMessagesForOpenAIResponsesInitialRound, extractInstructionsForOpenAIResponses, buildResponsesFunctionCallOutputItems, formatToolsForOpenAIResponses, formatMessagesForAnthropic, formatMessagesForGoogle, formatMessagesForMistral, formatMessagesForBedrock, } from "./streaming/llm-formatters.js";
16
+ import { streamWithOpenAIResponses, } from "./streaming/openai-responses.js";
16
17
  import { streamWithOpenAI, streamWithAnthropic, streamWithGoogle, streamWithGroq, streamWithCerebras, streamWithCohere, streamWithMistral, streamWithBedrock, streamWithDeepseek, streamWithXai, } from "./streaming/providers.js";
17
18
  // Optional imports for streaming LLM clients
18
19
  // These are peer dependencies and may not be installed
@@ -50,6 +51,9 @@ function normalizeToolCallForExecution(toolCall) {
50
51
  firstStatusAt: toolCall.firstStatusAt ?? undefined,
51
52
  };
52
53
  }
54
+ // Temporary rollout guard: keep default OpenAI Responses routing off until we
55
+ // intentionally flip the switch. Explicit `useResponsesApi: true` still forces it.
56
+ const OPENAI_RESPONSES_AUTO_ROUTING_ENABLED = false;
53
57
  function buildConversationToolCallFromResult(toolResult) {
54
58
  return {
55
59
  __typename: "ConversationToolCall",
@@ -5650,7 +5654,7 @@ class Graphlit {
5650
5654
  });
5651
5655
  // Start the streaming conversation
5652
5656
  // fullSpec is guaranteed non-null here — the !fullSpec check above returns early
5653
- await this.executeStreamingAgent(prompt, actualConversationId, fullSpec, tools, toolHandlers, uiAdapter, maxRounds, abortSignal, mimeType, data, correlationId, persona, options?.contextStrategy, options?.instructions, options?.scratchpad, options?.skills);
5657
+ await this.executeStreamingAgent(prompt, actualConversationId, fullSpec, tools, toolHandlers, uiAdapter, maxRounds, abortSignal, mimeType, data, correlationId, persona, options?.contextStrategy, options?.useResponsesApi, options?.instructions, options?.scratchpad, options?.skills);
5654
5658
  }, abortSignal);
5655
5659
  }
5656
5660
  catch (error) {
@@ -5696,7 +5700,7 @@ class Graphlit {
5696
5700
  /**
5697
5701
  * Execute the streaming agent workflow with tool calling loop
5698
5702
  */
5699
- async executeStreamingAgent(prompt, conversationId, specification, tools, toolHandlers, uiAdapter, maxRounds, abortSignal, mimeType, data, correlationId, persona, contextStrategy, instructions, scratchpad, skills) {
5703
+ async executeStreamingAgent(prompt, conversationId, specification, tools, toolHandlers, uiAdapter, maxRounds, abortSignal, mimeType, data, correlationId, persona, contextStrategy, useResponsesApi, instructions, scratchpad, skills) {
5700
5704
  // Collects artifact content IDs from tool handlers (e.g. code_execution).
5701
5705
  // Handlers register async ingestion promises; we await all of them before
5702
5706
  // completeConversation so the IDs are available without blocking the LLM.
@@ -5868,6 +5872,7 @@ class Graphlit {
5868
5872
  contextStrategy: mergedStrategy,
5869
5873
  maxRounds,
5870
5874
  abortSignal,
5875
+ useResponsesApi,
5871
5876
  correlationId,
5872
5877
  persona,
5873
5878
  mimeType,
@@ -5928,7 +5933,7 @@ class Graphlit {
5928
5933
  * Shared between streamAgent (single turn) and runAgent (multi-turn harness).
5929
5934
  */
5930
5935
  async executeStreamingLoop(config) {
5931
- const { conversationId, specification, tools, toolHandlers, uiAdapter, budgetTracker, contextStrategy: { toolResultTokenLimit, toolRoundLimit, rebudgetThreshold }, maxRounds, abortSignal, mimeType, data, correlationId, persona, } = config;
5936
+ const { conversationId, specification, tools, toolHandlers, uiAdapter, budgetTracker, contextStrategy: { toolResultTokenLimit, toolRoundLimit, rebudgetThreshold }, maxRounds, abortSignal, useResponsesApi, mimeType, data, correlationId, persona, } = config;
5932
5937
  let messages = config.messages;
5933
5938
  let currentRound = 0;
5934
5939
  let fullMessage = "";
@@ -5951,8 +5956,14 @@ class Graphlit {
5951
5956
  },
5952
5957
  };
5953
5958
  const serviceType = getServiceType(specification);
5959
+ const useOpenAIResponses = (useResponsesApi === true ||
5960
+ (OPENAI_RESPONSES_AUTO_ROUTING_ENABLED && useResponsesApi !== false)) &&
5961
+ serviceType === Types.ModelServiceTypes.OpenAi &&
5962
+ isOpenAIResponsesEligibleModel(specification);
5954
5963
  const toolMessagesStartIndex = messages.length;
5955
5964
  let lastRoundReasoning;
5965
+ let openAIResponsesState;
5966
+ let openAIResponsesPendingToolMessages = [];
5956
5967
  if (process.env.DEBUG_GRAPHLIT_SDK_STREAMING && budgetTracker) {
5957
5968
  console.log(`šŸ“Š [Context Management] Initialized budget tracker: ${budgetTracker.usagePercent}% used, ` +
5958
5969
  `${budgetTracker.remaining.toLocaleString()} tokens remaining. ` +
@@ -5970,6 +5981,10 @@ class Graphlit {
5970
5981
  const beforeCount = messages.length;
5971
5982
  messages = windowToolRounds(messages, toolRoundLimit);
5972
5983
  budgetTracker.resetFromMessages(messages);
5984
+ if (useOpenAIResponses) {
5985
+ openAIResponsesState = undefined;
5986
+ openAIResponsesPendingToolMessages = [];
5987
+ }
5973
5988
  const afterUsage = budgetTracker.usagePercent;
5974
5989
  const droppedRounds = Math.max(0, Math.floor((beforeCount - messages.length) / 2));
5975
5990
  if (droppedRounds > 0) {
@@ -6018,22 +6033,44 @@ class Graphlit {
6018
6033
  if (serviceType === Types.ModelServiceTypes.OpenAi &&
6019
6034
  (OpenAI || this.openaiClient)) {
6020
6035
  if (process.env.DEBUG_GRAPHLIT_SDK_STREAMING) {
6021
- console.log(`\nāœ… [Streaming] Using OpenAI native streaming (Round ${currentRound})`);
6036
+ console.log(`\nāœ… [Streaming] Using OpenAI ${useOpenAIResponses ? "Responses" : "native"} streaming (Round ${currentRound})`);
6022
6037
  }
6023
- const openaiMessages = formatMessagesForOpenAI(messages);
6024
- if (process.env.DEBUG_GRAPHLIT_SDK_STREAMING_MESSAGES) {
6025
- console.log(`šŸ” [OpenAI] Sending ${openaiMessages.length} messages to LLM: ${JSON.stringify(openaiMessages)}`);
6038
+ if (useOpenAIResponses) {
6039
+ if (!openAIResponsesState) {
6040
+ openAIResponsesState = {
6041
+ instructions: extractInstructionsForOpenAIResponses(messages),
6042
+ initialInput: formatMessagesForOpenAIResponsesInitialRound(messages),
6043
+ continuationItems: [],
6044
+ };
6045
+ }
6046
+ if (process.env.DEBUG_GRAPHLIT_SDK_STREAMING_MESSAGES) {
6047
+ console.log(`šŸ” [OpenAI Responses] Sending ${openAIResponsesState.initialInput.length} initial items and ${openAIResponsesState.continuationItems.length} continuation items`);
6048
+ }
6049
+ const responsesResult = await this.streamWithOpenAIResponses(specification, messages, openAIResponsesPendingToolMessages, tools, uiAdapter, abortSignal, openAIResponsesState);
6050
+ roundMessage = responsesResult.message;
6051
+ toolCalls = responsesResult.toolCalls;
6052
+ openAIResponsesState = responsesResult.state;
6053
+ if (responsesResult.usage) {
6054
+ uiAdapter.setUsageData(responsesResult.usage);
6055
+ }
6056
+ openAIResponsesPendingToolMessages = [];
6026
6057
  }
6027
- await this.streamWithOpenAI(specification, openaiMessages, tools, uiAdapter, (message, calls, usage, reasoning) => {
6028
- roundMessage = message;
6029
- toolCalls = calls;
6030
- roundReasoning = reasoning;
6031
- if (usage) {
6032
- uiAdapter.setUsageData(usage);
6058
+ else {
6059
+ const openaiMessages = formatMessagesForOpenAI(messages);
6060
+ if (process.env.DEBUG_GRAPHLIT_SDK_STREAMING_MESSAGES) {
6061
+ console.log(`šŸ” [OpenAI] Sending ${openaiMessages.length} messages to LLM: ${JSON.stringify(openaiMessages)}`);
6033
6062
  }
6034
- }, abortSignal);
6063
+ await this.streamWithOpenAI(specification, openaiMessages, tools, uiAdapter, (message, calls, usage, reasoning) => {
6064
+ roundMessage = message;
6065
+ toolCalls = calls;
6066
+ roundReasoning = reasoning;
6067
+ if (usage) {
6068
+ uiAdapter.setUsageData(usage);
6069
+ }
6070
+ }, abortSignal);
6071
+ }
6035
6072
  if (process.env.DEBUG_GRAPHLIT_SDK_STREAMING) {
6036
- console.log(`\nšŸ [Streaming] OpenAI native streaming completed (Round ${currentRound})`);
6073
+ console.log(`\nšŸ [Streaming] OpenAI ${useOpenAIResponses ? "Responses" : "native"} streaming completed (Round ${currentRound})`);
6037
6074
  }
6038
6075
  }
6039
6076
  else if (serviceType === Types.ModelServiceTypes.Anthropic &&
@@ -6620,6 +6657,11 @@ class Graphlit {
6620
6657
  budgetTracker.addMessage(executionResult.budgetText);
6621
6658
  }
6622
6659
  }
6660
+ if (useOpenAIResponses) {
6661
+ openAIResponsesPendingToolMessages = toolExecutionResults
6662
+ .map((executionResult) => executionResult?.toolMessage)
6663
+ .filter((message) => message != null);
6664
+ }
6623
6665
  }
6624
6666
  // Emit context window usage after each tool round
6625
6667
  if (budgetTracker) {
@@ -7074,6 +7116,7 @@ class Graphlit {
7074
7116
  contextStrategy: mergedStrategy,
7075
7117
  maxRounds: DEFAULT_MAX_TOOL_ROUNDS,
7076
7118
  abortSignal,
7119
+ useResponsesApi: options?.useResponsesApi,
7077
7120
  correlationId: options?.correlationId,
7078
7121
  persona: options?.persona,
7079
7122
  });
@@ -7558,6 +7601,52 @@ class Graphlit {
7558
7601
  }
7559
7602
  }
7560
7603
  }
7604
+ /**
7605
+ * Stream with OpenAI client
7606
+ */
7607
+ async streamWithOpenAIResponses(specification, messages, toolMessages, tools, uiAdapter, abortSignal, state) {
7608
+ if (!OpenAI && !this.openaiClient) {
7609
+ throw new Error("OpenAI client not available");
7610
+ }
7611
+ const openaiClient = this.openaiClient ||
7612
+ (OpenAI
7613
+ ? new OpenAI({
7614
+ apiKey: process.env.OPENAI_API_KEY || "",
7615
+ maxRetries: 3,
7616
+ timeout: 60000,
7617
+ })
7618
+ : (() => {
7619
+ throw new Error("OpenAI module not available");
7620
+ })());
7621
+ const reasoningEffort = specification.openAI?.reasoningEffort || undefined;
7622
+ const toolDefinitions = formatToolsForOpenAIResponses(tools);
7623
+ const baseState = state || {
7624
+ instructions: extractInstructionsForOpenAIResponses(messages),
7625
+ initialInput: formatMessagesForOpenAIResponsesInitialRound(messages),
7626
+ continuationItems: [],
7627
+ };
7628
+ const functionCallOutputs = buildResponsesFunctionCallOutputItems(toolMessages);
7629
+ const input = [
7630
+ ...baseState.initialInput,
7631
+ ...baseState.continuationItems,
7632
+ ...functionCallOutputs,
7633
+ ];
7634
+ const response = await streamWithOpenAIResponses(specification, baseState.instructions, input, toolDefinitions, openaiClient, (event) => uiAdapter.handleEvent(event), abortSignal, reasoningEffort);
7635
+ return {
7636
+ message: response.message,
7637
+ toolCalls: response.toolCalls,
7638
+ usage: response.usage,
7639
+ state: {
7640
+ instructions: baseState.instructions,
7641
+ initialInput: baseState.initialInput,
7642
+ continuationItems: [
7643
+ ...baseState.continuationItems,
7644
+ ...functionCallOutputs,
7645
+ ...response.outputItems,
7646
+ ],
7647
+ },
7648
+ };
7649
+ }
7561
7650
  /**
7562
7651
  * Stream with OpenAI client
7563
7652
  */
@@ -16597,6 +16597,7 @@ export const CreatePersona = gql `
16597
16597
  id
16598
16598
  name
16599
16599
  state
16600
+ type
16600
16601
  identifier
16601
16602
  platform
16602
16603
  displayName
@@ -16644,6 +16645,7 @@ export const GetPersona = gql `
16644
16645
  id
16645
16646
  }
16646
16647
  state
16648
+ type
16647
16649
  user {
16648
16650
  id
16649
16651
  }
@@ -16673,6 +16675,7 @@ export const QueryPersonas = gql `
16673
16675
  id
16674
16676
  }
16675
16677
  state
16678
+ type
16676
16679
  identifier
16677
16680
  platform
16678
16681
  displayName
@@ -16693,6 +16696,7 @@ export const UpdatePersona = gql `
16693
16696
  id
16694
16697
  name
16695
16698
  state
16699
+ type
16696
16700
  identifier
16697
16701
  platform
16698
16702
  displayName
@@ -18989,6 +18993,7 @@ export const GetUser = gql `
18989
18993
  id
18990
18994
  name
18991
18995
  state
18996
+ type
18992
18997
  identifier
18993
18998
  platform
18994
18999
  displayName
@@ -19117,6 +19122,7 @@ export const GetUserByIdentifier = gql `
19117
19122
  id
19118
19123
  name
19119
19124
  state
19125
+ type
19120
19126
  identifier
19121
19127
  platform
19122
19128
  displayName
@@ -19246,6 +19252,7 @@ export const QueryUsers = gql `
19246
19252
  id
19247
19253
  name
19248
19254
  state
19255
+ type
19249
19256
  identifier
19250
19257
  platform
19251
19258
  displayName
@@ -19156,7 +19156,7 @@ export type PersonUpdateInput = {
19156
19156
  /** The person URI. */
19157
19157
  uri?: InputMaybe<Scalars['URL']['input']>;
19158
19158
  };
19159
- /** Represents a user persona for per-user agent personalization. */
19159
+ /** Represents a persona for user or agent personalization. */
19160
19160
  export type Persona = {
19161
19161
  __typename?: 'Persona';
19162
19162
  /** The creation date of the persona. */
@@ -19187,6 +19187,8 @@ export type Persona = {
19187
19187
  state: EntityState;
19188
19188
  /** The IANA timezone of the persona. */
19189
19189
  timezone?: Maybe<Scalars['String']['output']>;
19190
+ /** The type of the persona. */
19191
+ type?: Maybe<PersonaTypes>;
19190
19192
  /** The user that created the entity. */
19191
19193
  user?: Maybe<EntityReference>;
19192
19194
  };
@@ -19220,6 +19222,8 @@ export type PersonaFilter = {
19220
19222
  search?: InputMaybe<Scalars['String']['input']>;
19221
19223
  /** Filter persona(s) by their states. */
19222
19224
  states?: InputMaybe<Array<EntityState>>;
19225
+ /** Filter by persona type. */
19226
+ types?: InputMaybe<Array<PersonaTypes>>;
19223
19227
  };
19224
19228
  /** Represents a persona. */
19225
19229
  export type PersonaInput = {
@@ -19237,6 +19241,8 @@ export type PersonaInput = {
19237
19241
  role?: InputMaybe<Scalars['String']['input']>;
19238
19242
  /** The IANA timezone of the persona. */
19239
19243
  timezone?: InputMaybe<Scalars['String']['input']>;
19244
+ /** The type of the persona. Defaults to User. */
19245
+ type?: InputMaybe<PersonaTypes>;
19240
19246
  };
19241
19247
  /** Represents persona query results. */
19242
19248
  export type PersonaResults = {
@@ -19244,6 +19250,13 @@ export type PersonaResults = {
19244
19250
  /** The list of persona query results. */
19245
19251
  results?: Maybe<Array<Persona>>;
19246
19252
  };
19253
+ /** Persona type */
19254
+ export declare enum PersonaTypes {
19255
+ /** Agent persona */
19256
+ Agent = "AGENT",
19257
+ /** User persona */
19258
+ User = "USER"
19259
+ }
19247
19260
  /** Represents a persona. */
19248
19261
  export type PersonaUpdateInput = {
19249
19262
  /** The display name of the persona. */
@@ -19262,6 +19275,8 @@ export type PersonaUpdateInput = {
19262
19275
  role?: InputMaybe<Scalars['String']['input']>;
19263
19276
  /** The IANA timezone of the persona. */
19264
19277
  timezone?: InputMaybe<Scalars['String']['input']>;
19278
+ /** The type of the persona. */
19279
+ type?: InputMaybe<PersonaTypes>;
19265
19280
  };
19266
19281
  /** Represents a place. */
19267
19282
  export type Place = {
@@ -44898,6 +44913,7 @@ export type CreatePersonaMutation = {
44898
44913
  id: string;
44899
44914
  name: string;
44900
44915
  state: EntityState;
44916
+ type?: PersonaTypes | null;
44901
44917
  identifier?: string | null;
44902
44918
  platform?: string | null;
44903
44919
  displayName?: string | null;
@@ -44954,6 +44970,7 @@ export type GetPersonaQuery = {
44954
44970
  creationDate: any;
44955
44971
  modifiedDate?: any | null;
44956
44972
  state: EntityState;
44973
+ type?: PersonaTypes | null;
44957
44974
  identifier?: string | null;
44958
44975
  platform?: string | null;
44959
44976
  displayName?: string | null;
@@ -44991,6 +45008,7 @@ export type QueryPersonasQuery = {
44991
45008
  modifiedDate?: any | null;
44992
45009
  relevance?: number | null;
44993
45010
  state: EntityState;
45011
+ type?: PersonaTypes | null;
44994
45012
  identifier?: string | null;
44995
45013
  platform?: string | null;
44996
45014
  displayName?: string | null;
@@ -45019,6 +45037,7 @@ export type UpdatePersonaMutation = {
45019
45037
  id: string;
45020
45038
  name: string;
45021
45039
  state: EntityState;
45040
+ type?: PersonaTypes | null;
45022
45041
  identifier?: string | null;
45023
45042
  platform?: string | null;
45024
45043
  displayName?: string | null;
@@ -47762,6 +47781,7 @@ export type GetUserQuery = {
47762
47781
  id: string;
47763
47782
  name: string;
47764
47783
  state: EntityState;
47784
+ type?: PersonaTypes | null;
47765
47785
  identifier?: string | null;
47766
47786
  platform?: string | null;
47767
47787
  displayName?: string | null;
@@ -47915,6 +47935,7 @@ export type GetUserByIdentifierQuery = {
47915
47935
  id: string;
47916
47936
  name: string;
47917
47937
  state: EntityState;
47938
+ type?: PersonaTypes | null;
47918
47939
  identifier?: string | null;
47919
47940
  platform?: string | null;
47920
47941
  displayName?: string | null;
@@ -48071,6 +48092,7 @@ export type QueryUsersQuery = {
48071
48092
  id: string;
48072
48093
  name: string;
48073
48094
  state: EntityState;
48095
+ type?: PersonaTypes | null;
48074
48096
  identifier?: string | null;
48075
48097
  platform?: string | null;
48076
48098
  displayName?: string | null;
@@ -2858,6 +2858,14 @@ export var PersonFacetTypes;
2858
2858
  /** Creation Date */
2859
2859
  PersonFacetTypes["CreationDate"] = "CREATION_DATE";
2860
2860
  })(PersonFacetTypes || (PersonFacetTypes = {}));
2861
+ /** Persona type */
2862
+ export var PersonaTypes;
2863
+ (function (PersonaTypes) {
2864
+ /** Agent persona */
2865
+ PersonaTypes["Agent"] = "AGENT";
2866
+ /** User persona */
2867
+ PersonaTypes["User"] = "USER";
2868
+ })(PersonaTypes || (PersonaTypes = {}));
2861
2869
  /** Place facet types */
2862
2870
  export var PlaceFacetTypes;
2863
2871
  (function (PlaceFacetTypes) {
@@ -22,3 +22,9 @@ export declare function getServiceType(specification: any): string | undefined;
22
22
  * @returns The model enum value
23
23
  */
24
24
  export declare function getModelEnum(specification: any): string | undefined;
25
+ /**
26
+ * Check whether an OpenAI specification should use the Responses API path.
27
+ * Eligible models are GPT-5.4 and newer.
28
+ */
29
+ export declare function isOpenAIResponsesEligibleModel(specification: any): boolean;
30
+ export declare const shouldUseOpenAIResponsesModel: typeof isOpenAIResponsesEligibleModel;
@@ -66,6 +66,14 @@ const OPENAI_MODEL_MAP = {
66
66
  [Types.OpenAiModels.O4Mini_200K]: "o4-mini",
67
67
  [Types.OpenAiModels.O4Mini_200K_20250416]: "o4-mini-2025-04-16",
68
68
  };
69
+ const OPENAI_RESPONSES_MODEL_ENUMS = new Set([
70
+ Types.OpenAiModels.Gpt54_1024K,
71
+ Types.OpenAiModels.Gpt54_1024K_20260305,
72
+ Types.OpenAiModels.Gpt54Mini_400K,
73
+ Types.OpenAiModels.Gpt54Mini_400K_20260317,
74
+ Types.OpenAiModels.Gpt54Nano_400K,
75
+ Types.OpenAiModels.Gpt54Nano_400K_20260317,
76
+ ]);
69
77
  // Anthropic model mappings
70
78
  const ANTHROPIC_MODEL_MAP = {
71
79
  // Claude 3 models
@@ -342,3 +350,31 @@ export function getModelEnum(specification) {
342
350
  return undefined;
343
351
  }
344
352
  }
353
+ /**
354
+ * Check whether an OpenAI specification should use the Responses API path.
355
+ * Eligible models are GPT-5.4 and newer.
356
+ */
357
+ export function isOpenAIResponsesEligibleModel(specification) {
358
+ if (specification?.serviceType !== Types.ModelServiceTypes.OpenAi) {
359
+ return false;
360
+ }
361
+ const modelEnum = getModelEnum(specification);
362
+ if (modelEnum && OPENAI_RESPONSES_MODEL_ENUMS.has(modelEnum)) {
363
+ return true;
364
+ }
365
+ const modelName = getModelName(specification);
366
+ if (!modelName) {
367
+ return false;
368
+ }
369
+ const match = modelName.match(/^gpt-(\d+)(?:\.(\d+))?(?:-[a-z0-9]+)?(?:-\d{4}-\d{2}-\d{2})?$/);
370
+ if (!match) {
371
+ return false;
372
+ }
373
+ const major = Number(match[1]);
374
+ const minor = match[2] ? Number(match[2]) : 0;
375
+ if (!Number.isFinite(major) || !Number.isFinite(minor)) {
376
+ return false;
377
+ }
378
+ return major > 5 || (major === 5 && minor >= 4);
379
+ }
380
+ export const shouldUseOpenAIResponsesModel = isOpenAIResponsesEligibleModel;
@@ -21,6 +21,42 @@ export interface OpenAIMessage {
21
21
  }>;
22
22
  tool_call_id?: string;
23
23
  }
24
+ export interface OpenAIResponsesTextContentPart {
25
+ type: "input_text";
26
+ text: string;
27
+ }
28
+ export interface OpenAIResponsesImageContentPart {
29
+ type: "input_image";
30
+ image_url: string;
31
+ detail: "low" | "high" | "auto";
32
+ }
33
+ export type OpenAIResponsesContentPart = OpenAIResponsesTextContentPart | OpenAIResponsesImageContentPart;
34
+ export interface OpenAIResponsesMessageItem {
35
+ type: "message";
36
+ role: "user" | "assistant";
37
+ content: string | OpenAIResponsesContentPart[];
38
+ }
39
+ export interface OpenAIResponsesFunctionCallItem {
40
+ type: "function_call";
41
+ call_id: string;
42
+ name: string;
43
+ arguments: string;
44
+ id?: string;
45
+ }
46
+ export interface OpenAIResponsesFunctionCallOutputItem {
47
+ type: "function_call_output";
48
+ call_id: string;
49
+ output: string;
50
+ id?: string;
51
+ }
52
+ export type OpenAIResponsesInputItem = OpenAIResponsesMessageItem | OpenAIResponsesFunctionCallItem | OpenAIResponsesFunctionCallOutputItem | Record<string, unknown>;
53
+ export interface OpenAIResponsesToolDefinition {
54
+ type: "function";
55
+ name: string;
56
+ description?: string;
57
+ parameters: Record<string, unknown>;
58
+ strict: false;
59
+ }
24
60
  /**
25
61
  * Anthropic message format
26
62
  */
@@ -74,6 +110,15 @@ export interface GoogleMessage {
74
110
  * Format GraphQL conversation messages for OpenAI SDK
75
111
  */
76
112
  export declare function formatMessagesForOpenAI(messages: ConversationMessage[]): OpenAIMessage[];
113
+ export declare function extractInstructionsForOpenAIResponses(messages: ConversationMessage[]): string | undefined;
114
+ export declare function formatMessagesForOpenAIResponsesInitialRound(messages: ConversationMessage[]): OpenAIResponsesInputItem[];
115
+ export declare function buildResponsesFunctionCallOutputItems(toolMessages: ConversationMessage[]): OpenAIResponsesFunctionCallOutputItem[];
116
+ export declare const buildOpenAIResponsesFunctionCallOutputItems: typeof buildResponsesFunctionCallOutputItems;
117
+ export declare function formatToolsForOpenAIResponses(tools: Array<{
118
+ name: string;
119
+ description?: string | null;
120
+ schema?: string | null;
121
+ }> | undefined): OpenAIResponsesToolDefinition[] | undefined;
77
122
  /**
78
123
  * Format GraphQL conversation messages for Anthropic SDK
79
124
  */
@@ -190,6 +190,130 @@ export function formatMessagesForOpenAI(messages) {
190
190
  }
191
191
  return formattedMessages;
192
192
  }
193
+ export function extractInstructionsForOpenAIResponses(messages) {
194
+ const systemMessages = messages
195
+ .filter((message) => message.role === ConversationRoleTypes.System)
196
+ .map((message) => message.message?.trim() || "")
197
+ .filter((message) => message.length > 0);
198
+ return systemMessages.length > 0
199
+ ? systemMessages.join("\n\n")
200
+ : undefined;
201
+ }
202
+ export function formatMessagesForOpenAIResponsesInitialRound(messages) {
203
+ const formattedMessages = [];
204
+ for (const message of messages) {
205
+ if (!message.role || message.role === ConversationRoleTypes.System) {
206
+ continue;
207
+ }
208
+ const trimmedMessage = message.message?.trim() || "";
209
+ const hasToolCalls = !!message.toolCalls?.length;
210
+ if (!trimmedMessage && !hasToolCalls) {
211
+ continue;
212
+ }
213
+ switch (message.role) {
214
+ case ConversationRoleTypes.Assistant: {
215
+ if (trimmedMessage) {
216
+ formattedMessages.push({
217
+ type: "message",
218
+ role: "assistant",
219
+ content: trimmedMessage,
220
+ });
221
+ }
222
+ if (message.toolCalls?.length) {
223
+ for (const toolCall of message.toolCalls) {
224
+ if (!toolCall) {
225
+ continue;
226
+ }
227
+ formattedMessages.push({
228
+ type: "function_call",
229
+ id: toolCall.id,
230
+ call_id: toolCall.id,
231
+ name: toolCall.name,
232
+ arguments: toolCall.arguments,
233
+ });
234
+ }
235
+ }
236
+ break;
237
+ }
238
+ case ConversationRoleTypes.Tool: {
239
+ if (!message.toolCallId) {
240
+ continue;
241
+ }
242
+ formattedMessages.push({
243
+ type: "function_call_output",
244
+ call_id: message.toolCallId,
245
+ output: stripImagesToText(trimmedMessage),
246
+ });
247
+ break;
248
+ }
249
+ default: {
250
+ if (message.mimeType && message.data) {
251
+ const contentParts = [];
252
+ if (trimmedMessage) {
253
+ contentParts.push({
254
+ type: "input_text",
255
+ text: trimmedMessage,
256
+ });
257
+ }
258
+ contentParts.push({
259
+ type: "input_image",
260
+ image_url: `data:${message.mimeType};base64,${message.data}`,
261
+ detail: "auto",
262
+ });
263
+ formattedMessages.push({
264
+ type: "message",
265
+ role: "user",
266
+ content: contentParts,
267
+ });
268
+ }
269
+ else {
270
+ formattedMessages.push({
271
+ type: "message",
272
+ role: "user",
273
+ content: trimmedMessage,
274
+ });
275
+ }
276
+ break;
277
+ }
278
+ }
279
+ }
280
+ return formattedMessages;
281
+ }
282
+ export function buildResponsesFunctionCallOutputItems(toolMessages) {
283
+ return toolMessages
284
+ .filter((message) => message.role === ConversationRoleTypes.Tool &&
285
+ typeof message.toolCallId === "string" &&
286
+ message.toolCallId.length > 0)
287
+ .map((message) => ({
288
+ type: "function_call_output",
289
+ call_id: message.toolCallId,
290
+ output: stripImagesToText(message.message?.trim() || ""),
291
+ }));
292
+ }
293
+ export const buildOpenAIResponsesFunctionCallOutputItems = buildResponsesFunctionCallOutputItems;
294
+ export function formatToolsForOpenAIResponses(tools) {
295
+ if (!tools?.length) {
296
+ return undefined;
297
+ }
298
+ return tools.map((tool) => {
299
+ let parameters = {};
300
+ if (tool.schema) {
301
+ try {
302
+ parameters = JSON.parse(tool.schema);
303
+ }
304
+ catch {
305
+ parameters = {};
306
+ }
307
+ }
308
+ return {
309
+ type: "function",
310
+ name: tool.name,
311
+ description: tool.description || undefined,
312
+ parameters,
313
+ strict: false,
314
+ };
315
+ });
316
+ }
193
317
  /**
194
318
  * Format GraphQL conversation messages for Anthropic SDK
195
319
  */
@@ -0,0 +1,10 @@
1
+ import { ConversationToolCall, Specification } from "../generated/graphql-types.js";
2
+ import { StreamEvent } from "../types/internal.js";
3
+ import type { OpenAIResponsesInputItem, OpenAIResponsesToolDefinition } from "./llm-formatters.js";
4
+ export interface OpenAIResponsesRoundResult {
5
+ message: string;
6
+ toolCalls: ConversationToolCall[];
7
+ usage?: unknown;
8
+ outputItems: OpenAIResponsesInputItem[];
9
+ }
10
+ export declare function streamWithOpenAIResponses(specification: Specification, instructions: string | undefined, input: OpenAIResponsesInputItem[], tools: OpenAIResponsesToolDefinition[] | undefined, openaiClient: any, onEvent: (event: StreamEvent) => void, abortSignal?: AbortSignal, reasoningEffort?: string): Promise<OpenAIResponsesRoundResult>;
@@ -0,0 +1,221 @@
1
+ import { getModelName } from "../model-mapping.js";
2
+ import { ProviderError, extractRequestId, isNetworkError, isRateLimitError, isRetryableServerError, } from "../types/internal.js";
3
+ function toToolCallId(item, outputIndex) {
4
+ return item.call_id || item.id || `tool_${Date.now()}_${outputIndex}`;
5
+ }
6
+ export async function streamWithOpenAIResponses(specification, instructions, input, tools, openaiClient, onEvent, abortSignal, reasoningEffort) {
7
+ let fullMessage = "";
8
+ let tokenCount = 0;
9
+ let usageData;
10
+ let outputItems = [];
11
+ const toolCallsByIndex = new Map();
12
+ const toolCallIdsByItemId = new Map();
13
+ const startedToolCallIds = new Set();
14
+ const parsedToolCallIds = new Set();
15
+ const getOrCreateToolCall = (outputIndex, item) => {
16
+ const existing = toolCallsByIndex.get(outputIndex);
17
+ if (existing) {
18
+ if (item?.name) {
19
+ existing.name = item.name;
20
+ }
21
+ if (typeof item?.arguments === "string" && !existing.arguments) {
22
+ existing.arguments = item.arguments;
23
+ }
24
+ return existing;
25
+ }
26
+ const toolCall = {
27
+ __typename: "ConversationToolCall",
28
+ id: toToolCallId(item || {}, outputIndex),
29
+ name: item?.name || "",
30
+ arguments: item?.arguments || "",
31
+ };
32
+ toolCallsByIndex.set(outputIndex, toolCall);
33
+ if (item?.id) {
34
+ toolCallIdsByItemId.set(item.id, toolCall.id);
35
+ }
36
+ return toolCall;
37
+ };
38
+ const emitToolCallStartIfNeeded = (toolCall) => {
39
+ if (startedToolCallIds.has(toolCall.id)) {
40
+ return;
41
+ }
42
+ startedToolCallIds.add(toolCall.id);
43
+ onEvent({
44
+ type: "tool_call_start",
45
+ toolCall: {
46
+ id: toolCall.id,
47
+ name: toolCall.name,
48
+ },
49
+ });
50
+ };
51
+ const emitToolCallParsedIfNeeded = (toolCall) => {
52
+ if (!toolCall.arguments || parsedToolCallIds.has(toolCall.id)) {
53
+ return;
54
+ }
55
+ parsedToolCallIds.add(toolCall.id);
56
+ onEvent({
57
+ type: "tool_call_parsed",
58
+ toolCall: {
59
+ id: toolCall.id,
60
+ name: toolCall.name,
61
+ arguments: toolCall.arguments,
62
+ },
63
+ });
64
+ };
65
+ try {
66
+ const modelName = getModelName(specification);
67
+ if (!modelName) {
68
+ throw new Error(`No model name found for specification: ${specification.name} (service: ${specification.serviceType})`);
69
+ }
70
+ const request = {
71
+ model: modelName,
72
+ input,
73
+ stream: true,
74
+ store: false,
75
+ include: ["reasoning.encrypted_content"],
76
+ };
77
+ if (instructions?.trim()) {
78
+ request.instructions = instructions.trim();
79
+ }
80
+ if (specification.openAI?.temperature !== undefined) {
81
+ request.temperature = specification.openAI.temperature;
82
+ }
83
+ if (specification.openAI?.completionTokenLimit) {
84
+ request.max_output_tokens = specification.openAI.completionTokenLimit;
85
+ }
86
+ if (tools?.length) {
87
+ request.tools = tools;
88
+ }
89
+ if (reasoningEffort) {
90
+ request.reasoning = { effort: reasoningEffort.toLowerCase() };
91
+ }
92
+ const stream = await openaiClient.responses.create(request, {
93
+ signal: abortSignal,
94
+ });
95
+ for await (const event of stream) {
96
+ switch (event.type) {
97
+ case "response.output_text.delta":
98
+ if (event.delta) {
99
+ fullMessage += event.delta;
100
+ tokenCount++;
101
+ onEvent({ type: "token", token: event.delta });
102
+ }
103
+ break;
104
+ case "response.output_item.added": {
105
+ const item = event.item;
106
+ if (item?.type !== "function_call") {
107
+ break;
108
+ }
109
+ const toolCall = getOrCreateToolCall(event.output_index, item);
110
+ emitToolCallStartIfNeeded(toolCall);
111
+ break;
112
+ }
113
+ case "response.function_call_arguments.delta": {
114
+ const toolCallId = toolCallIdsByItemId.get(event.item_id) ||
115
+ toolCallsByIndex.get(event.output_index)?.id;
116
+ const toolCall = toolCallsByIndex.get(event.output_index);
117
+ if (!toolCall || !toolCallId) {
118
+ break;
119
+ }
120
+ toolCall.arguments += event.delta;
121
+ onEvent({
122
+ type: "tool_call_delta",
123
+ toolCallId,
124
+ argumentDelta: event.delta,
125
+ });
126
+ break;
127
+ }
128
+ case "response.function_call_arguments.done": {
129
+ const toolCall = toolCallsByIndex.get(event.output_index);
130
+ if (!toolCall) {
131
+ break;
132
+ }
133
+ toolCall.arguments = event.arguments;
134
+ emitToolCallParsedIfNeeded(toolCall);
135
+ break;
136
+ }
137
+ case "response.output_item.done": {
138
+ const item = event.item;
139
+ if (item?.type !== "function_call") {
140
+ break;
141
+ }
142
+ const toolCall = getOrCreateToolCall(event.output_index, item);
143
+ if (item.id) {
144
+ toolCallIdsByItemId.set(item.id, toolCall.id);
145
+ }
146
+ emitToolCallStartIfNeeded(toolCall);
147
+ if (typeof item.arguments === "string") {
148
+ toolCall.arguments = item.arguments;
149
+ emitToolCallParsedIfNeeded(toolCall);
150
+ }
151
+ break;
152
+ }
153
+ case "response.completed":
154
+ usageData = event.response?.usage;
155
+ outputItems = (event.response?.output || []);
156
+ if (!fullMessage && typeof event.response?.output_text === "string") {
157
+ fullMessage = event.response.output_text;
158
+ }
159
+ break;
160
+ case "error":
161
+ case "response.error":
162
+ throw new Error(event.error?.message || "OpenAI Responses streaming error");
163
+ case "response.failed":
164
+ throw new Error(event.response?.error?.message || "OpenAI Responses request failed");
165
+ }
166
+ }
167
+ const orderedToolCalls = Array.from(toolCallsByIndex.entries())
168
+ .sort(([left], [right]) => left - right)
169
+ .map(([, toolCall]) => toolCall);
170
+ for (const toolCall of orderedToolCalls) {
171
+ emitToolCallParsedIfNeeded(toolCall);
172
+ }
173
+ onEvent({
174
+ type: "complete",
175
+ tokens: tokenCount,
176
+ });
177
+ return {
178
+ message: fullMessage,
179
+ toolCalls: orderedToolCalls,
180
+ usage: usageData,
181
+ outputItems,
182
+ };
183
+ }
184
+ catch (error) {
185
+ const errorMessage = error.message || error.toString();
186
+ if (isRateLimitError(error)) {
187
+ throw new ProviderError(`OpenAI rate limit exceeded: ${errorMessage}`, {
188
+ provider: "openai",
189
+ statusCode: 429,
190
+ retryable: true,
191
+ requestId: extractRequestId(error),
192
+ cause: error,
193
+ });
194
+ }
195
+ if (isNetworkError(error)) {
196
+ throw new ProviderError(`OpenAI network error: ${errorMessage}`, {
197
+ provider: "openai",
198
+ statusCode: error.status ?? 0,
199
+ retryable: true,
200
+ requestId: extractRequestId(error),
201
+ cause: error,
202
+ });
203
+ }
204
+ if (isRetryableServerError(error)) {
205
+ throw new ProviderError(`OpenAI server error: ${errorMessage}`, {
206
+ provider: "openai",
207
+ statusCode: error.status ?? error.statusCode ?? 500,
208
+ retryable: true,
209
+ requestId: extractRequestId(error),
210
+ cause: error,
211
+ });
212
+ }
213
+ throw new ProviderError(`OpenAI error: ${errorMessage}`, {
214
+ provider: "openai",
215
+ statusCode: error.status ?? error.statusCode ?? 400,
216
+ retryable: false,
217
+ requestId: extractRequestId(error),
218
+ cause: error,
219
+ });
220
+ }
221
+ }
@@ -66,6 +66,8 @@ export interface StreamAgentOptions {
66
66
  chunkingStrategy?: "character" | "word" | "sentence";
67
67
  smoothingDelay?: number;
68
68
  contextStrategy?: ContextStrategy;
69
+ /** Explicitly route eligible OpenAI GPT-5.4+ models through Responses; false forces legacy Chat Completions. */
70
+ useResponsesApi?: boolean;
69
71
  /** Harness-injected instructions appended to the formatted conversation (e.g. wind-down, stuck intervention). */
70
72
  instructions?: string;
71
73
  /** Harness-injected scratchpad text, merged with conversation scratchpad. Placed right before user-prompt. */
@@ -157,6 +159,7 @@ export interface RunAgentOptions {
157
159
  smoothingEnabled?: boolean;
158
160
  chunkingStrategy?: "character" | "word" | "sentence";
159
161
  smoothingDelay?: number;
162
+ useResponsesApi?: boolean;
160
163
  /** Skill references to inject into the agent's context window. Retrieved via querySkills, passed by reference. */
161
164
  skills?: EntityReferenceInput[];
162
165
  }
@@ -205,6 +208,7 @@ export interface StreamingLoopConfig {
205
208
  };
206
209
  maxRounds: number;
207
210
  abortSignal: AbortSignal | undefined;
211
+ useResponsesApi?: boolean;
208
212
  correlationId?: string;
209
213
  persona?: EntityReferenceInput;
210
214
  mimeType?: string;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "graphlit-client",
3
- "version": "1.0.20260406002",
3
+ "version": "1.0.20260408001",
4
4
  "description": "Graphlit API Client for TypeScript",
5
5
  "type": "module",
6
6
  "main": "./dist/client.js",
@@ -85,7 +85,7 @@
85
85
  "cohere-ai": "^7.20.0",
86
86
  "groq-sdk": "^0.25.0",
87
87
  "js-tiktoken": "^1.0.16",
88
- "openai": "^5.3.0"
88
+ "openai": "^5.23.2"
89
89
  },
90
90
  "devDependencies": {
91
91
  "@graphql-codegen/typescript-document-nodes": "^5.0.7",