@gitlab/gitlab-ai-provider 3.1.3 → 3.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.mjs CHANGED
@@ -1,4 +1,4 @@
1
- // src/gitlab-agentic-language-model.ts
1
+ // src/gitlab-anthropic-language-model.ts
2
2
  import Anthropic from "@anthropic-ai/sdk";
3
3
 
4
4
  // src/gitlab-direct-access.ts
@@ -133,6 +133,15 @@ var GitLabDirectAccessClient = class {
133
133
  const baseUrl = this.aiGatewayUrl.replace(/\/$/, "");
134
134
  return `${baseUrl}/ai/v1/proxy/anthropic/`;
135
135
  }
136
+ /**
137
+ * Get the OpenAI proxy base URL
138
+ * Note: The OpenAI SDK expects a base URL like https://api.openai.com/v1
139
+ * and appends paths like /chat/completions. So we need /v1 at the end.
140
+ */
141
+ getOpenAIProxyUrl() {
142
+ const baseUrl = this.aiGatewayUrl.replace(/\/$/, "");
143
+ return `${baseUrl}/ai/v1/proxy/openai/v1`;
144
+ }
136
145
  /**
137
146
  * Invalidate the cached token
138
147
  */
@@ -142,8 +151,8 @@ var GitLabDirectAccessClient = class {
142
151
  }
143
152
  };
144
153
 
145
- // src/gitlab-agentic-language-model.ts
146
- var GitLabAgenticLanguageModel = class {
154
+ // src/gitlab-anthropic-language-model.ts
155
+ var GitLabAnthropicLanguageModel = class {
147
156
  specificationVersion = "v2";
148
157
  modelId;
149
158
  supportedUrls = {};
@@ -596,6 +605,741 @@ var GitLabAgenticLanguageModel = class {
596
605
  }
597
606
  };
598
607
 
608
+ // src/gitlab-openai-language-model.ts
609
+ import OpenAI from "openai";
610
+
611
+ // src/model-mappings.ts
612
+ var MODEL_MAPPINGS = {
613
+ // Anthropic models
614
+ "duo-chat-opus-4-5": { provider: "anthropic", model: "claude-opus-4-5-20251101" },
615
+ "duo-chat-sonnet-4-5": { provider: "anthropic", model: "claude-sonnet-4-5-20250929" },
616
+ "duo-chat-haiku-4-5": { provider: "anthropic", model: "claude-haiku-4-5-20251001" },
617
+ // OpenAI models - Chat Completions API
618
+ "duo-chat-gpt-5-1": { provider: "openai", model: "gpt-5.1-2025-11-13", openaiApiType: "chat" },
619
+ "duo-chat-gpt-5-2": { provider: "openai", model: "gpt-5.2-2025-12-11", openaiApiType: "chat" },
620
+ "duo-chat-gpt-5-mini": {
621
+ provider: "openai",
622
+ model: "gpt-5-mini-2025-08-07",
623
+ openaiApiType: "chat"
624
+ },
625
+ // OpenAI models - Responses API (Codex models)
626
+ "duo-chat-gpt-5-codex": { provider: "openai", model: "gpt-5-codex", openaiApiType: "responses" },
627
+ "duo-chat-gpt-5-2-codex": {
628
+ provider: "openai",
629
+ model: "gpt-5.2-codex",
630
+ openaiApiType: "responses"
631
+ }
632
+ };
633
+ function getModelMapping(modelId) {
634
+ return MODEL_MAPPINGS[modelId];
635
+ }
636
+ function getProviderForModelId(modelId) {
637
+ return MODEL_MAPPINGS[modelId]?.provider;
638
+ }
639
+ function getValidModelsForProvider(provider) {
640
+ return Object.values(MODEL_MAPPINGS).filter((m) => m.provider === provider).map((m) => m.model);
641
+ }
642
+ function getAnthropicModelForModelId(modelId) {
643
+ const mapping = MODEL_MAPPINGS[modelId];
644
+ return mapping?.provider === "anthropic" ? mapping.model : void 0;
645
+ }
646
+ function getOpenAIModelForModelId(modelId) {
647
+ const mapping = MODEL_MAPPINGS[modelId];
648
+ return mapping?.provider === "openai" ? mapping.model : void 0;
649
+ }
650
+ function getOpenAIApiType(modelId) {
651
+ const mapping = MODEL_MAPPINGS[modelId];
652
+ return mapping?.openaiApiType ?? "chat";
653
+ }
654
+ function isResponsesApiModel(modelId) {
655
+ return getOpenAIApiType(modelId) === "responses";
656
+ }
657
+ var MODEL_ID_TO_ANTHROPIC_MODEL = Object.fromEntries(
658
+ Object.entries(MODEL_MAPPINGS).filter(([, v]) => v.provider === "anthropic").map(([k, v]) => [k, v.model])
659
+ );
660
+
661
+ // src/gitlab-openai-language-model.ts
662
+ var GitLabOpenAILanguageModel = class {
663
+ specificationVersion = "v2";
664
+ modelId;
665
+ supportedUrls = {};
666
+ config;
667
+ directAccessClient;
668
+ useResponsesApi;
669
+ openaiClient = null;
670
+ constructor(modelId, config) {
671
+ this.modelId = modelId;
672
+ this.config = config;
673
+ this.useResponsesApi = config.useResponsesApi ?? isResponsesApiModel(modelId);
674
+ this.directAccessClient = new GitLabDirectAccessClient({
675
+ instanceUrl: config.instanceUrl,
676
+ getHeaders: config.getHeaders,
677
+ refreshApiKey: config.refreshApiKey,
678
+ fetch: config.fetch,
679
+ featureFlags: config.featureFlags,
680
+ aiGatewayUrl: config.aiGatewayUrl
681
+ });
682
+ }
683
+ get provider() {
684
+ return this.config.provider;
685
+ }
686
+ async getOpenAIClient(forceRefresh = false) {
687
+ const tokenData = await this.directAccessClient.getDirectAccessToken(forceRefresh);
688
+ const { "x-api-key": _removed, ...filteredHeaders } = tokenData.headers;
689
+ this.openaiClient = new OpenAI({
690
+ apiKey: tokenData.token,
691
+ baseURL: this.directAccessClient.getOpenAIProxyUrl(),
692
+ defaultHeaders: filteredHeaders
693
+ });
694
+ return this.openaiClient;
695
+ }
696
+ isTokenError(error) {
697
+ if (error instanceof OpenAI.APIError) {
698
+ if (error.status === 401) {
699
+ return true;
700
+ }
701
+ const message = error.message?.toLowerCase() || "";
702
+ if (message.includes("token") && (message.includes("expired") || message.includes("revoked") || message.includes("invalid"))) {
703
+ return true;
704
+ }
705
+ }
706
+ return false;
707
+ }
708
+ convertTools(tools) {
709
+ if (!tools || tools.length === 0) {
710
+ return void 0;
711
+ }
712
+ return tools.filter((tool) => tool.type === "function").map((tool) => {
713
+ const schema = tool.inputSchema;
714
+ return {
715
+ type: "function",
716
+ function: {
717
+ name: tool.name,
718
+ description: tool.description || "",
719
+ // Ensure the schema has type: 'object' as OpenAI requires it
720
+ parameters: {
721
+ type: "object",
722
+ ...schema
723
+ }
724
+ }
725
+ };
726
+ });
727
+ }
728
+ convertToolChoice(toolChoice) {
729
+ if (!toolChoice) {
730
+ return void 0;
731
+ }
732
+ switch (toolChoice.type) {
733
+ case "auto":
734
+ return "auto";
735
+ case "none":
736
+ return "none";
737
+ case "required":
738
+ return "required";
739
+ case "tool":
740
+ return { type: "function", function: { name: toolChoice.toolName } };
741
+ default:
742
+ return void 0;
743
+ }
744
+ }
745
+ convertPrompt(prompt) {
746
+ const messages = [];
747
+ for (const message of prompt) {
748
+ if (message.role === "system") {
749
+ messages.push({ role: "system", content: message.content });
750
+ continue;
751
+ }
752
+ if (message.role === "user") {
753
+ const textParts = message.content.filter((part) => part.type === "text").map((part) => part.text);
754
+ if (textParts.length > 0) {
755
+ messages.push({ role: "user", content: textParts.join("\n") });
756
+ }
757
+ } else if (message.role === "assistant") {
758
+ const textParts = [];
759
+ const toolCalls = [];
760
+ for (const part of message.content) {
761
+ if (part.type === "text") {
762
+ textParts.push(part.text);
763
+ } else if (part.type === "tool-call") {
764
+ toolCalls.push({
765
+ id: part.toolCallId,
766
+ type: "function",
767
+ function: {
768
+ name: part.toolName,
769
+ arguments: typeof part.input === "string" ? part.input : JSON.stringify(part.input)
770
+ }
771
+ });
772
+ }
773
+ }
774
+ const assistantMessage = {
775
+ role: "assistant",
776
+ content: textParts.length > 0 ? textParts.join("\n") : null
777
+ };
778
+ if (toolCalls.length > 0) {
779
+ assistantMessage.tool_calls = toolCalls;
780
+ }
781
+ messages.push(assistantMessage);
782
+ } else if (message.role === "tool") {
783
+ for (const part of message.content) {
784
+ if (part.type === "tool-result") {
785
+ let resultContent;
786
+ if (part.output.type === "text") {
787
+ resultContent = part.output.value;
788
+ } else if (part.output.type === "json") {
789
+ resultContent = JSON.stringify(part.output.value);
790
+ } else if (part.output.type === "error-text") {
791
+ resultContent = part.output.value;
792
+ } else if (part.output.type === "error-json") {
793
+ resultContent = JSON.stringify(part.output.value);
794
+ } else {
795
+ resultContent = JSON.stringify(part.output);
796
+ }
797
+ messages.push({
798
+ role: "tool",
799
+ tool_call_id: part.toolCallId,
800
+ content: resultContent
801
+ });
802
+ }
803
+ }
804
+ }
805
+ }
806
+ return messages;
807
+ }
808
+ convertFinishReason(finishReason) {
809
+ switch (finishReason) {
810
+ case "stop":
811
+ return "stop";
812
+ case "length":
813
+ return "length";
814
+ case "tool_calls":
815
+ return "tool-calls";
816
+ case "content_filter":
817
+ return "content-filter";
818
+ default:
819
+ return "unknown";
820
+ }
821
+ }
822
+ /**
823
+ * Convert tools to Responses API format
824
+ */
825
+ convertToolsForResponses(tools) {
826
+ if (!tools || tools.length === 0) {
827
+ return void 0;
828
+ }
829
+ return tools.filter((tool) => tool.type === "function").map((tool) => {
830
+ const schema = { ...tool.inputSchema };
831
+ delete schema["$schema"];
832
+ return {
833
+ type: "function",
834
+ name: tool.name,
835
+ description: tool.description || "",
836
+ parameters: schema,
837
+ strict: false
838
+ };
839
+ });
840
+ }
841
+ /**
842
+ * Convert prompt to Responses API input format
843
+ */
844
+ convertPromptForResponses(prompt) {
845
+ const items = [];
846
+ for (const message of prompt) {
847
+ if (message.role === "system") {
848
+ continue;
849
+ }
850
+ if (message.role === "user") {
851
+ const textParts = message.content.filter((part) => part.type === "text").map((part) => part.text);
852
+ if (textParts.length > 0) {
853
+ items.push({
854
+ type: "message",
855
+ role: "user",
856
+ content: textParts.map((text) => ({ type: "input_text", text }))
857
+ });
858
+ }
859
+ } else if (message.role === "assistant") {
860
+ const textParts = [];
861
+ for (const part of message.content) {
862
+ if (part.type === "text") {
863
+ textParts.push(part.text);
864
+ } else if (part.type === "tool-call") {
865
+ items.push({
866
+ type: "function_call",
867
+ call_id: part.toolCallId,
868
+ name: part.toolName,
869
+ arguments: typeof part.input === "string" ? part.input : JSON.stringify(part.input)
870
+ });
871
+ }
872
+ }
873
+ if (textParts.length > 0) {
874
+ items.push({
875
+ type: "message",
876
+ role: "assistant",
877
+ content: [{ type: "output_text", text: textParts.join("\n"), annotations: [] }]
878
+ });
879
+ }
880
+ } else if (message.role === "tool") {
881
+ for (const part of message.content) {
882
+ if (part.type === "tool-result") {
883
+ let resultContent;
884
+ if (part.output.type === "text") {
885
+ resultContent = part.output.value;
886
+ } else if (part.output.type === "json") {
887
+ resultContent = JSON.stringify(part.output.value);
888
+ } else if (part.output.type === "error-text") {
889
+ resultContent = part.output.value;
890
+ } else if (part.output.type === "error-json") {
891
+ resultContent = JSON.stringify(part.output.value);
892
+ } else {
893
+ resultContent = JSON.stringify(part.output);
894
+ }
895
+ items.push({
896
+ type: "function_call_output",
897
+ call_id: part.toolCallId,
898
+ output: resultContent
899
+ });
900
+ }
901
+ }
902
+ }
903
+ }
904
+ return items;
905
+ }
906
+ /**
907
+ * Extract system instructions from prompt
908
+ */
909
+ extractSystemInstructions(prompt) {
910
+ const systemMessages = prompt.filter((m) => m.role === "system").map((m) => m.content).join("\n");
911
+ return systemMessages || void 0;
912
+ }
913
+ /**
914
+ * Convert Responses API status to finish reason
915
+ * Note: Responses API returns 'completed' even when making tool calls,
916
+ * so we need to check the content for tool calls separately.
917
+ */
918
+ convertResponsesStatus(status, hasToolCalls = false) {
919
+ if (hasToolCalls) {
920
+ return "tool-calls";
921
+ }
922
+ switch (status) {
923
+ case "completed":
924
+ return "stop";
925
+ case "incomplete":
926
+ return "length";
927
+ case "cancelled":
928
+ return "stop";
929
+ case "failed":
930
+ return "error";
931
+ default:
932
+ return "unknown";
933
+ }
934
+ }
935
+ async doGenerate(options) {
936
+ if (this.useResponsesApi) {
937
+ return this.doGenerateWithResponsesApi(options, false);
938
+ }
939
+ return this.doGenerateWithChatApi(options, false);
940
+ }
941
+ async doGenerateWithChatApi(options, isRetry) {
942
+ const client = await this.getOpenAIClient(isRetry);
943
+ const messages = this.convertPrompt(options.prompt);
944
+ const tools = this.convertTools(options.tools);
945
+ const toolChoice = options.toolChoice?.type !== "none" ? this.convertToolChoice(options.toolChoice) : void 0;
946
+ const openaiModel = this.config.openaiModel || "gpt-4o";
947
+ const maxTokens = options.maxOutputTokens || this.config.maxTokens || 8192;
948
+ try {
949
+ const response = await client.chat.completions.create({
950
+ model: openaiModel,
951
+ max_completion_tokens: maxTokens,
952
+ messages,
953
+ tools,
954
+ tool_choice: tools ? toolChoice : void 0,
955
+ temperature: options.temperature,
956
+ top_p: options.topP,
957
+ stop: options.stopSequences
958
+ });
959
+ const choice = response.choices[0];
960
+ const content = [];
961
+ if (choice?.message.content) {
962
+ content.push({ type: "text", text: choice.message.content });
963
+ }
964
+ if (choice?.message.tool_calls) {
965
+ for (const toolCall of choice.message.tool_calls) {
966
+ if (toolCall.type === "function") {
967
+ content.push({
968
+ type: "tool-call",
969
+ toolCallId: toolCall.id,
970
+ toolName: toolCall.function.name,
971
+ input: toolCall.function.arguments
972
+ });
973
+ }
974
+ }
975
+ }
976
+ const usage = {
977
+ inputTokens: response.usage?.prompt_tokens || 0,
978
+ outputTokens: response.usage?.completion_tokens || 0,
979
+ totalTokens: response.usage?.total_tokens || 0
980
+ };
981
+ return {
982
+ content,
983
+ finishReason: this.convertFinishReason(choice?.finish_reason),
984
+ usage,
985
+ warnings: []
986
+ };
987
+ } catch (error) {
988
+ if (!isRetry && this.isTokenError(error)) {
989
+ this.directAccessClient.invalidateToken();
990
+ return this.doGenerateWithChatApi(options, true);
991
+ }
992
+ if (error instanceof OpenAI.APIError) {
993
+ throw new GitLabError({
994
+ message: `OpenAI API error: ${error.message}`,
995
+ cause: error
996
+ });
997
+ }
998
+ throw error;
999
+ }
1000
+ }
1001
+ async doGenerateWithResponsesApi(options, isRetry) {
1002
+ const client = await this.getOpenAIClient(isRetry);
1003
+ const input = this.convertPromptForResponses(options.prompt);
1004
+ const tools = this.convertToolsForResponses(options.tools);
1005
+ const instructions = this.extractSystemInstructions(options.prompt);
1006
+ const openaiModel = this.config.openaiModel || "gpt-5-codex";
1007
+ const maxTokens = options.maxOutputTokens || this.config.maxTokens || 8192;
1008
+ try {
1009
+ const response = await client.responses.create({
1010
+ model: openaiModel,
1011
+ input,
1012
+ instructions,
1013
+ tools,
1014
+ max_output_tokens: maxTokens,
1015
+ temperature: options.temperature,
1016
+ top_p: options.topP,
1017
+ store: false
1018
+ });
1019
+ const content = [];
1020
+ let hasToolCalls = false;
1021
+ for (const item of response.output || []) {
1022
+ if (item.type === "message" && item.role === "assistant") {
1023
+ for (const contentItem of item.content || []) {
1024
+ if (contentItem.type === "output_text") {
1025
+ content.push({ type: "text", text: contentItem.text });
1026
+ }
1027
+ }
1028
+ } else if (item.type === "function_call") {
1029
+ hasToolCalls = true;
1030
+ content.push({
1031
+ type: "tool-call",
1032
+ toolCallId: item.call_id,
1033
+ toolName: item.name,
1034
+ input: item.arguments
1035
+ });
1036
+ }
1037
+ }
1038
+ const usage = {
1039
+ inputTokens: response.usage?.input_tokens || 0,
1040
+ outputTokens: response.usage?.output_tokens || 0,
1041
+ totalTokens: response.usage?.total_tokens || 0
1042
+ };
1043
+ return {
1044
+ content,
1045
+ finishReason: this.convertResponsesStatus(response.status, hasToolCalls),
1046
+ usage,
1047
+ warnings: []
1048
+ };
1049
+ } catch (error) {
1050
+ if (!isRetry && this.isTokenError(error)) {
1051
+ this.directAccessClient.invalidateToken();
1052
+ return this.doGenerateWithResponsesApi(options, true);
1053
+ }
1054
+ if (error instanceof OpenAI.APIError) {
1055
+ throw new GitLabError({
1056
+ message: `OpenAI API error: ${error.message}`,
1057
+ cause: error
1058
+ });
1059
+ }
1060
+ throw error;
1061
+ }
1062
+ }
1063
+ async doStream(options) {
1064
+ if (this.useResponsesApi) {
1065
+ return this.doStreamWithResponsesApi(options, false);
1066
+ }
1067
+ return this.doStreamWithChatApi(options, false);
1068
+ }
1069
+ async doStreamWithChatApi(options, isRetry) {
1070
+ const client = await this.getOpenAIClient(isRetry);
1071
+ const messages = this.convertPrompt(options.prompt);
1072
+ const tools = this.convertTools(options.tools);
1073
+ const toolChoice = options.toolChoice?.type !== "none" ? this.convertToolChoice(options.toolChoice) : void 0;
1074
+ const openaiModel = this.config.openaiModel || "gpt-4o";
1075
+ const maxTokens = options.maxOutputTokens || this.config.maxTokens || 8192;
1076
+ const requestBody = {
1077
+ model: openaiModel,
1078
+ max_completion_tokens: maxTokens,
1079
+ messages,
1080
+ tools,
1081
+ tool_choice: tools ? toolChoice : void 0,
1082
+ temperature: options.temperature,
1083
+ top_p: options.topP,
1084
+ stop: options.stopSequences,
1085
+ stream: true,
1086
+ stream_options: { include_usage: true }
1087
+ };
1088
+ const self = this;
1089
+ const stream = new ReadableStream({
1090
+ start: async (controller) => {
1091
+ const toolCalls = {};
1092
+ const usage = {
1093
+ inputTokens: 0,
1094
+ outputTokens: 0,
1095
+ totalTokens: 0
1096
+ };
1097
+ let finishReason = "unknown";
1098
+ let textStarted = false;
1099
+ const textId = "text-0";
1100
+ try {
1101
+ const openaiStream = await client.chat.completions.create({
1102
+ ...requestBody,
1103
+ stream: true
1104
+ });
1105
+ controller.enqueue({ type: "stream-start", warnings: [] });
1106
+ for await (const chunk of openaiStream) {
1107
+ const choice = chunk.choices?.[0];
1108
+ if (chunk.id && !textStarted) {
1109
+ controller.enqueue({
1110
+ type: "response-metadata",
1111
+ id: chunk.id,
1112
+ modelId: chunk.model
1113
+ });
1114
+ }
1115
+ if (choice?.delta?.content) {
1116
+ if (!textStarted) {
1117
+ controller.enqueue({ type: "text-start", id: textId });
1118
+ textStarted = true;
1119
+ }
1120
+ controller.enqueue({
1121
+ type: "text-delta",
1122
+ id: textId,
1123
+ delta: choice.delta.content
1124
+ });
1125
+ }
1126
+ if (choice?.delta?.tool_calls) {
1127
+ for (const tc of choice.delta.tool_calls) {
1128
+ const idx = tc.index;
1129
+ if (!toolCalls[idx]) {
1130
+ toolCalls[idx] = {
1131
+ id: tc.id || "",
1132
+ name: tc.function?.name || "",
1133
+ arguments: ""
1134
+ };
1135
+ controller.enqueue({
1136
+ type: "tool-input-start",
1137
+ id: toolCalls[idx].id,
1138
+ toolName: toolCalls[idx].name
1139
+ });
1140
+ }
1141
+ if (tc.function?.arguments) {
1142
+ toolCalls[idx].arguments += tc.function.arguments;
1143
+ controller.enqueue({
1144
+ type: "tool-input-delta",
1145
+ id: toolCalls[idx].id,
1146
+ delta: tc.function.arguments
1147
+ });
1148
+ }
1149
+ }
1150
+ }
1151
+ if (choice?.finish_reason) {
1152
+ finishReason = self.convertFinishReason(choice.finish_reason);
1153
+ }
1154
+ if (chunk.usage) {
1155
+ usage.inputTokens = chunk.usage.prompt_tokens || 0;
1156
+ usage.outputTokens = chunk.usage.completion_tokens || 0;
1157
+ usage.totalTokens = chunk.usage.total_tokens || 0;
1158
+ }
1159
+ }
1160
+ if (textStarted) {
1161
+ controller.enqueue({ type: "text-end", id: textId });
1162
+ }
1163
+ for (const [, tc] of Object.entries(toolCalls)) {
1164
+ controller.enqueue({ type: "tool-input-end", id: tc.id });
1165
+ controller.enqueue({
1166
+ type: "tool-call",
1167
+ toolCallId: tc.id,
1168
+ toolName: tc.name,
1169
+ input: tc.arguments || "{}"
1170
+ });
1171
+ }
1172
+ controller.enqueue({ type: "finish", finishReason, usage });
1173
+ controller.close();
1174
+ } catch (error) {
1175
+ if (!isRetry && self.isTokenError(error)) {
1176
+ self.directAccessClient.invalidateToken();
1177
+ controller.enqueue({
1178
+ type: "error",
1179
+ error: new GitLabError({ message: "TOKEN_REFRESH_NEEDED", cause: error })
1180
+ });
1181
+ controller.close();
1182
+ return;
1183
+ }
1184
+ if (error instanceof OpenAI.APIError) {
1185
+ controller.enqueue({
1186
+ type: "error",
1187
+ error: new GitLabError({
1188
+ message: `OpenAI API error: ${error.message}`,
1189
+ cause: error
1190
+ })
1191
+ });
1192
+ } else {
1193
+ controller.enqueue({ type: "error", error });
1194
+ }
1195
+ controller.close();
1196
+ }
1197
+ }
1198
+ });
1199
+ return { stream, request: { body: requestBody } };
1200
+ }
1201
+ async doStreamWithResponsesApi(options, isRetry) {
1202
+ const client = await this.getOpenAIClient(isRetry);
1203
+ const input = this.convertPromptForResponses(options.prompt);
1204
+ const tools = this.convertToolsForResponses(options.tools);
1205
+ const instructions = this.extractSystemInstructions(options.prompt);
1206
+ const openaiModel = this.config.openaiModel || "gpt-5-codex";
1207
+ const maxTokens = options.maxOutputTokens || this.config.maxTokens || 8192;
1208
+ const requestBody = {
1209
+ model: openaiModel,
1210
+ input,
1211
+ instructions,
1212
+ tools,
1213
+ max_output_tokens: maxTokens,
1214
+ temperature: options.temperature,
1215
+ top_p: options.topP,
1216
+ store: false,
1217
+ stream: true
1218
+ };
1219
+ const self = this;
1220
+ const stream = new ReadableStream({
1221
+ start: async (controller) => {
1222
+ const toolCalls = {};
1223
+ const usage = {
1224
+ inputTokens: 0,
1225
+ outputTokens: 0,
1226
+ totalTokens: 0
1227
+ };
1228
+ let finishReason = "unknown";
1229
+ let textStarted = false;
1230
+ const textId = "text-0";
1231
+ try {
1232
+ const openaiStream = await client.responses.create({
1233
+ ...requestBody,
1234
+ stream: true
1235
+ });
1236
+ controller.enqueue({ type: "stream-start", warnings: [] });
1237
+ for await (const event of openaiStream) {
1238
+ if (event.type === "response.created") {
1239
+ controller.enqueue({
1240
+ type: "response-metadata",
1241
+ id: event.response.id,
1242
+ modelId: event.response.model
1243
+ });
1244
+ } else if (event.type === "response.output_item.added") {
1245
+ if (event.item.type === "function_call") {
1246
+ const outputIndex = event.output_index;
1247
+ const callId = event.item.call_id;
1248
+ toolCalls[outputIndex] = {
1249
+ callId,
1250
+ name: event.item.name,
1251
+ arguments: ""
1252
+ };
1253
+ controller.enqueue({
1254
+ type: "tool-input-start",
1255
+ id: callId,
1256
+ toolName: event.item.name
1257
+ });
1258
+ }
1259
+ } else if (event.type === "response.output_text.delta") {
1260
+ if (!textStarted) {
1261
+ controller.enqueue({ type: "text-start", id: textId });
1262
+ textStarted = true;
1263
+ }
1264
+ controller.enqueue({
1265
+ type: "text-delta",
1266
+ id: textId,
1267
+ delta: event.delta
1268
+ });
1269
+ } else if (event.type === "response.function_call_arguments.delta") {
1270
+ const outputIndex = event.output_index;
1271
+ const tc = toolCalls[outputIndex];
1272
+ if (tc) {
1273
+ tc.arguments += event.delta;
1274
+ controller.enqueue({
1275
+ type: "tool-input-delta",
1276
+ id: tc.callId,
1277
+ delta: event.delta
1278
+ });
1279
+ }
1280
+ } else if (event.type === "response.function_call_arguments.done") {
1281
+ const outputIndex = event.output_index;
1282
+ const tc = toolCalls[outputIndex];
1283
+ if (tc) {
1284
+ tc.arguments = event.arguments;
1285
+ }
1286
+ } else if (event.type === "response.completed") {
1287
+ const hasToolCalls2 = Object.keys(toolCalls).length > 0;
1288
+ finishReason = self.convertResponsesStatus(event.response.status, hasToolCalls2);
1289
+ if (event.response.usage) {
1290
+ usage.inputTokens = event.response.usage.input_tokens || 0;
1291
+ usage.outputTokens = event.response.usage.output_tokens || 0;
1292
+ usage.totalTokens = event.response.usage.total_tokens || 0;
1293
+ }
1294
+ }
1295
+ }
1296
+ if (textStarted) {
1297
+ controller.enqueue({ type: "text-end", id: textId });
1298
+ }
1299
+ const hasToolCalls = Object.keys(toolCalls).length > 0;
1300
+ if (hasToolCalls && finishReason === "stop") {
1301
+ finishReason = "tool-calls";
1302
+ }
1303
+ for (const tc of Object.values(toolCalls)) {
1304
+ controller.enqueue({ type: "tool-input-end", id: tc.callId });
1305
+ controller.enqueue({
1306
+ type: "tool-call",
1307
+ toolCallId: tc.callId,
1308
+ toolName: tc.name,
1309
+ input: tc.arguments || "{}"
1310
+ });
1311
+ }
1312
+ controller.enqueue({ type: "finish", finishReason, usage });
1313
+ controller.close();
1314
+ } catch (error) {
1315
+ if (!isRetry && self.isTokenError(error)) {
1316
+ self.directAccessClient.invalidateToken();
1317
+ controller.enqueue({
1318
+ type: "error",
1319
+ error: new GitLabError({ message: "TOKEN_REFRESH_NEEDED", cause: error })
1320
+ });
1321
+ controller.close();
1322
+ return;
1323
+ }
1324
+ if (error instanceof OpenAI.APIError) {
1325
+ controller.enqueue({
1326
+ type: "error",
1327
+ error: new GitLabError({
1328
+ message: `OpenAI API error: ${error.message}`,
1329
+ cause: error
1330
+ })
1331
+ });
1332
+ } else {
1333
+ controller.enqueue({ type: "error", error });
1334
+ }
1335
+ controller.close();
1336
+ }
1337
+ }
1338
+ });
1339
+ return { stream, request: { body: requestBody } };
1340
+ }
1341
+ };
1342
+
599
1343
  // src/gitlab-oauth-types.ts
600
1344
  var BUNDLED_CLIENT_ID = "36f2a70cddeb5a0889d4fd8295c241b7e9848e89cf9e599d0eed2d8e5350fbf5";
601
1345
  var GITLAB_COM_URL = "https://gitlab.com";
@@ -758,16 +1502,6 @@ var GitLabOAuthManager = class {
758
1502
  }
759
1503
  };
760
1504
 
761
- // src/model-mappings.ts
762
- var MODEL_ID_TO_ANTHROPIC_MODEL = {
763
- "duo-chat-opus-4-5": "claude-opus-4-5-20251101",
764
- "duo-chat-sonnet-4-5": "claude-sonnet-4-5-20250929",
765
- "duo-chat-haiku-4-5": "claude-haiku-4-5-20251001"
766
- };
767
- function getAnthropicModelForModelId(modelId) {
768
- return MODEL_ID_TO_ANTHROPIC_MODEL[modelId];
769
- }
770
-
771
1505
  // src/gitlab-provider.ts
772
1506
  import * as fs from "fs";
773
1507
  import * as path from "path";
@@ -903,21 +1637,44 @@ function createGitLab(options = {}) {
903
1637
  getApiKey().catch(() => {
904
1638
  });
905
1639
  const createAgenticChatModel = (modelId, agenticOptions) => {
1640
+ const mapping = getModelMapping(modelId);
1641
+ if (!mapping) {
1642
+ throw new GitLabError({
1643
+ message: `Unknown model ID: ${modelId}. Model must be registered in MODEL_MAPPINGS.`
1644
+ });
1645
+ }
1646
+ if (agenticOptions?.providerModel) {
1647
+ const validModels = getValidModelsForProvider(mapping.provider);
1648
+ if (!validModels.includes(agenticOptions.providerModel)) {
1649
+ throw new GitLabError({
1650
+ message: `Invalid providerModel '${agenticOptions.providerModel}' for provider '${mapping.provider}'. Valid models: ${validModels.join(", ")}`
1651
+ });
1652
+ }
1653
+ }
906
1654
  const featureFlags = {
907
1655
  DuoAgentPlatformNext: true,
908
1656
  ...options.featureFlags,
909
1657
  ...agenticOptions?.featureFlags
910
1658
  };
911
- return new GitLabAgenticLanguageModel(modelId, {
1659
+ const baseConfig = {
912
1660
  provider: `${providerName}.agentic`,
913
1661
  instanceUrl,
914
1662
  getHeaders,
915
1663
  refreshApiKey,
916
1664
  fetch: options.fetch,
917
- anthropicModel: agenticOptions?.anthropicModel ?? getAnthropicModelForModelId(modelId),
918
1665
  maxTokens: agenticOptions?.maxTokens,
919
1666
  featureFlags,
920
1667
  aiGatewayUrl: options.aiGatewayUrl
1668
+ };
1669
+ if (mapping.provider === "openai") {
1670
+ return new GitLabOpenAILanguageModel(modelId, {
1671
+ ...baseConfig,
1672
+ openaiModel: agenticOptions?.providerModel ?? mapping.model
1673
+ });
1674
+ }
1675
+ return new GitLabAnthropicLanguageModel(modelId, {
1676
+ ...baseConfig,
1677
+ anthropicModel: agenticOptions?.providerModel ?? mapping.model
921
1678
  });
922
1679
  };
923
1680
  const createDefaultModel = (modelId) => {
@@ -1216,17 +1973,25 @@ export {
1216
1973
  BUNDLED_CLIENT_ID,
1217
1974
  DEFAULT_AI_GATEWAY_URL,
1218
1975
  GITLAB_COM_URL,
1219
- GitLabAgenticLanguageModel,
1976
+ GitLabAnthropicLanguageModel,
1220
1977
  GitLabDirectAccessClient,
1221
1978
  GitLabError,
1222
1979
  GitLabOAuthManager,
1980
+ GitLabOpenAILanguageModel,
1223
1981
  GitLabProjectCache,
1224
1982
  GitLabProjectDetector,
1225
1983
  MODEL_ID_TO_ANTHROPIC_MODEL,
1984
+ MODEL_MAPPINGS,
1226
1985
  OAUTH_SCOPES,
1227
1986
  TOKEN_EXPIRY_SKEW_MS,
1228
1987
  createGitLab,
1229
1988
  getAnthropicModelForModelId,
1230
- gitlab
1989
+ getModelMapping,
1990
+ getOpenAIApiType,
1991
+ getOpenAIModelForModelId,
1992
+ getProviderForModelId,
1993
+ getValidModelsForProvider,
1994
+ gitlab,
1995
+ isResponsesApiModel
1231
1996
  };
1232
1997
  //# sourceMappingURL=index.mjs.map