@gitlab/gitlab-ai-provider 3.1.2 → 3.2.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/CHANGELOG.md +9 -0
- package/README.md +117 -29
- package/dist/gitlab-gitlab-ai-provider-3.2.0.tgz +0 -0
- package/dist/index.d.mts +113 -46
- package/dist/index.d.ts +113 -46
- package/dist/index.js +937 -133
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +927 -131
- package/dist/index.mjs.map +1 -1
- package/package.json +2 -1
- package/dist/gitlab-gitlab-ai-provider-3.1.2.tgz +0 -0
package/dist/index.js
CHANGED
|
@@ -32,22 +32,30 @@ __export(index_exports, {
|
|
|
32
32
|
BUNDLED_CLIENT_ID: () => BUNDLED_CLIENT_ID,
|
|
33
33
|
DEFAULT_AI_GATEWAY_URL: () => DEFAULT_AI_GATEWAY_URL,
|
|
34
34
|
GITLAB_COM_URL: () => GITLAB_COM_URL,
|
|
35
|
-
|
|
35
|
+
GitLabAnthropicLanguageModel: () => GitLabAnthropicLanguageModel,
|
|
36
36
|
GitLabDirectAccessClient: () => GitLabDirectAccessClient,
|
|
37
37
|
GitLabError: () => GitLabError,
|
|
38
38
|
GitLabOAuthManager: () => GitLabOAuthManager,
|
|
39
|
+
GitLabOpenAILanguageModel: () => GitLabOpenAILanguageModel,
|
|
39
40
|
GitLabProjectCache: () => GitLabProjectCache,
|
|
40
41
|
GitLabProjectDetector: () => GitLabProjectDetector,
|
|
41
42
|
MODEL_ID_TO_ANTHROPIC_MODEL: () => MODEL_ID_TO_ANTHROPIC_MODEL,
|
|
43
|
+
MODEL_MAPPINGS: () => MODEL_MAPPINGS,
|
|
42
44
|
OAUTH_SCOPES: () => OAUTH_SCOPES,
|
|
43
45
|
TOKEN_EXPIRY_SKEW_MS: () => TOKEN_EXPIRY_SKEW_MS,
|
|
44
46
|
createGitLab: () => createGitLab,
|
|
45
47
|
getAnthropicModelForModelId: () => getAnthropicModelForModelId,
|
|
46
|
-
|
|
48
|
+
getModelMapping: () => getModelMapping,
|
|
49
|
+
getOpenAIApiType: () => getOpenAIApiType,
|
|
50
|
+
getOpenAIModelForModelId: () => getOpenAIModelForModelId,
|
|
51
|
+
getProviderForModelId: () => getProviderForModelId,
|
|
52
|
+
getValidModelsForProvider: () => getValidModelsForProvider,
|
|
53
|
+
gitlab: () => gitlab,
|
|
54
|
+
isResponsesApiModel: () => isResponsesApiModel
|
|
47
55
|
});
|
|
48
56
|
module.exports = __toCommonJS(index_exports);
|
|
49
57
|
|
|
50
|
-
// src/gitlab-
|
|
58
|
+
// src/gitlab-anthropic-language-model.ts
|
|
51
59
|
var import_sdk = __toESM(require("@anthropic-ai/sdk"));
|
|
52
60
|
|
|
53
61
|
// src/gitlab-direct-access.ts
|
|
@@ -182,6 +190,15 @@ var GitLabDirectAccessClient = class {
|
|
|
182
190
|
const baseUrl = this.aiGatewayUrl.replace(/\/$/, "");
|
|
183
191
|
return `${baseUrl}/ai/v1/proxy/anthropic/`;
|
|
184
192
|
}
|
|
193
|
+
/**
|
|
194
|
+
* Get the OpenAI proxy base URL
|
|
195
|
+
* Note: The OpenAI SDK expects a base URL like https://api.openai.com/v1
|
|
196
|
+
* and appends paths like /chat/completions. So we need /v1 at the end.
|
|
197
|
+
*/
|
|
198
|
+
getOpenAIProxyUrl() {
|
|
199
|
+
const baseUrl = this.aiGatewayUrl.replace(/\/$/, "");
|
|
200
|
+
return `${baseUrl}/ai/v1/proxy/openai/v1`;
|
|
201
|
+
}
|
|
185
202
|
/**
|
|
186
203
|
* Invalidate the cached token
|
|
187
204
|
*/
|
|
@@ -191,10 +208,8 @@ var GitLabDirectAccessClient = class {
|
|
|
191
208
|
}
|
|
192
209
|
};
|
|
193
210
|
|
|
194
|
-
// src/gitlab-
|
|
195
|
-
var
|
|
196
|
-
};
|
|
197
|
-
var GitLabAgenticLanguageModel = class {
|
|
211
|
+
// src/gitlab-anthropic-language-model.ts
|
|
212
|
+
var GitLabAnthropicLanguageModel = class {
|
|
198
213
|
specificationVersion = "v2";
|
|
199
214
|
modelId;
|
|
200
215
|
supportedUrls = {};
|
|
@@ -222,14 +237,7 @@ var GitLabAgenticLanguageModel = class {
|
|
|
222
237
|
*/
|
|
223
238
|
async getAnthropicClient(forceRefresh = false) {
|
|
224
239
|
const tokenData = await this.directAccessClient.getDirectAccessToken(forceRefresh);
|
|
225
|
-
debugLog("[gitlab-ai-provider] Token headers from GitLab:", tokenData.headers);
|
|
226
|
-
debugLog("[gitlab-ai-provider] Proxy URL:", this.directAccessClient.getAnthropicProxyUrl());
|
|
227
240
|
const { "x-api-key": _removed, ...filteredHeaders } = tokenData.headers;
|
|
228
|
-
if (_removed) {
|
|
229
|
-
debugLog(
|
|
230
|
-
"[gitlab-ai-provider] Filtered out x-api-key from headers (using authToken instead)"
|
|
231
|
-
);
|
|
232
|
-
}
|
|
233
241
|
this.anthropicClient = new import_sdk.default({
|
|
234
242
|
apiKey: null,
|
|
235
243
|
authToken: tokenData.token,
|
|
@@ -468,114 +476,155 @@ var GitLabAgenticLanguageModel = class {
|
|
|
468
476
|
const self = this;
|
|
469
477
|
const stream = new ReadableStream({
|
|
470
478
|
start: async (controller) => {
|
|
479
|
+
const contentBlocks = {};
|
|
480
|
+
const usage = {
|
|
481
|
+
inputTokens: 0,
|
|
482
|
+
outputTokens: 0,
|
|
483
|
+
totalTokens: 0
|
|
484
|
+
};
|
|
485
|
+
let finishReason = "unknown";
|
|
471
486
|
try {
|
|
472
|
-
const anthropicStream = client.messages.stream(requestBody
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
let currentToolName = null;
|
|
476
|
-
const usage = {
|
|
477
|
-
inputTokens: 0,
|
|
478
|
-
outputTokens: 0,
|
|
479
|
-
totalTokens: 0
|
|
480
|
-
};
|
|
481
|
-
let finishReason = "unknown";
|
|
487
|
+
const anthropicStream = client.messages.stream(requestBody, {
|
|
488
|
+
signal: options.abortSignal
|
|
489
|
+
});
|
|
482
490
|
controller.enqueue({
|
|
483
491
|
type: "stream-start",
|
|
484
492
|
warnings: []
|
|
485
493
|
});
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
type
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
494
|
+
await new Promise((resolve2, reject) => {
|
|
495
|
+
anthropicStream.on("streamEvent", (event) => {
|
|
496
|
+
try {
|
|
497
|
+
switch (event.type) {
|
|
498
|
+
case "message_start":
|
|
499
|
+
if (event.message.usage) {
|
|
500
|
+
usage.inputTokens = event.message.usage.input_tokens;
|
|
501
|
+
}
|
|
502
|
+
controller.enqueue({
|
|
503
|
+
type: "response-metadata",
|
|
504
|
+
id: event.message.id,
|
|
505
|
+
modelId: event.message.model
|
|
506
|
+
});
|
|
507
|
+
break;
|
|
508
|
+
case "content_block_start":
|
|
509
|
+
if (event.content_block.type === "text") {
|
|
510
|
+
const textId = `text-${event.index}`;
|
|
511
|
+
contentBlocks[event.index] = { type: "text", id: textId };
|
|
512
|
+
controller.enqueue({
|
|
513
|
+
type: "text-start",
|
|
514
|
+
id: textId
|
|
515
|
+
});
|
|
516
|
+
} else if (event.content_block.type === "tool_use") {
|
|
517
|
+
contentBlocks[event.index] = {
|
|
518
|
+
type: "tool-call",
|
|
519
|
+
toolCallId: event.content_block.id,
|
|
520
|
+
toolName: event.content_block.name,
|
|
521
|
+
input: ""
|
|
522
|
+
};
|
|
523
|
+
controller.enqueue({
|
|
524
|
+
type: "tool-input-start",
|
|
525
|
+
id: event.content_block.id,
|
|
526
|
+
toolName: event.content_block.name
|
|
527
|
+
});
|
|
528
|
+
}
|
|
529
|
+
break;
|
|
530
|
+
case "content_block_delta": {
|
|
531
|
+
const block = contentBlocks[event.index];
|
|
532
|
+
if (event.delta.type === "text_delta" && block?.type === "text") {
|
|
533
|
+
controller.enqueue({
|
|
534
|
+
type: "text-delta",
|
|
535
|
+
id: block.id,
|
|
536
|
+
delta: event.delta.text
|
|
537
|
+
});
|
|
538
|
+
} else if (event.delta.type === "input_json_delta" && block?.type === "tool-call") {
|
|
539
|
+
block.input += event.delta.partial_json;
|
|
540
|
+
controller.enqueue({
|
|
541
|
+
type: "tool-input-delta",
|
|
542
|
+
id: block.toolCallId,
|
|
543
|
+
delta: event.delta.partial_json
|
|
544
|
+
});
|
|
545
|
+
}
|
|
546
|
+
break;
|
|
547
|
+
}
|
|
548
|
+
case "content_block_stop": {
|
|
549
|
+
const block = contentBlocks[event.index];
|
|
550
|
+
if (block?.type === "text") {
|
|
551
|
+
controller.enqueue({
|
|
552
|
+
type: "text-end",
|
|
553
|
+
id: block.id
|
|
554
|
+
});
|
|
555
|
+
} else if (block?.type === "tool-call") {
|
|
556
|
+
controller.enqueue({
|
|
557
|
+
type: "tool-input-end",
|
|
558
|
+
id: block.toolCallId
|
|
559
|
+
});
|
|
560
|
+
controller.enqueue({
|
|
561
|
+
type: "tool-call",
|
|
562
|
+
toolCallId: block.toolCallId,
|
|
563
|
+
toolName: block.toolName,
|
|
564
|
+
input: block.input === "" ? "{}" : block.input
|
|
565
|
+
});
|
|
566
|
+
}
|
|
567
|
+
delete contentBlocks[event.index];
|
|
568
|
+
break;
|
|
569
|
+
}
|
|
570
|
+
case "message_delta":
|
|
571
|
+
if (event.usage) {
|
|
572
|
+
usage.outputTokens = event.usage.output_tokens;
|
|
573
|
+
usage.totalTokens = (usage.inputTokens || 0) + event.usage.output_tokens;
|
|
574
|
+
}
|
|
575
|
+
if (event.delta.stop_reason) {
|
|
576
|
+
finishReason = self.convertFinishReason(event.delta.stop_reason);
|
|
577
|
+
}
|
|
578
|
+
break;
|
|
579
|
+
case "message_stop": {
|
|
560
580
|
controller.enqueue({
|
|
561
|
-
type: "
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
input: JSON.stringify(block.input)
|
|
581
|
+
type: "finish",
|
|
582
|
+
finishReason,
|
|
583
|
+
usage
|
|
565
584
|
});
|
|
585
|
+
break;
|
|
566
586
|
}
|
|
567
587
|
}
|
|
568
|
-
|
|
569
|
-
type: "finish",
|
|
570
|
-
finishReason,
|
|
571
|
-
usage
|
|
572
|
-
});
|
|
573
|
-
break;
|
|
588
|
+
} catch {
|
|
574
589
|
}
|
|
590
|
+
});
|
|
591
|
+
anthropicStream.on("end", () => {
|
|
592
|
+
resolve2();
|
|
593
|
+
});
|
|
594
|
+
anthropicStream.on("error", (error) => {
|
|
595
|
+
reject(error);
|
|
596
|
+
});
|
|
597
|
+
});
|
|
598
|
+
for (const [, block] of Object.entries(contentBlocks)) {
|
|
599
|
+
if (block.type === "tool-call") {
|
|
600
|
+
controller.enqueue({
|
|
601
|
+
type: "tool-input-end",
|
|
602
|
+
id: block.toolCallId
|
|
603
|
+
});
|
|
604
|
+
controller.enqueue({
|
|
605
|
+
type: "tool-call",
|
|
606
|
+
toolCallId: block.toolCallId,
|
|
607
|
+
toolName: block.toolName,
|
|
608
|
+
input: block.input === "" ? "{}" : block.input
|
|
609
|
+
});
|
|
575
610
|
}
|
|
576
611
|
}
|
|
577
612
|
controller.close();
|
|
578
613
|
} catch (error) {
|
|
614
|
+
for (const [, block] of Object.entries(contentBlocks)) {
|
|
615
|
+
if (block.type === "tool-call") {
|
|
616
|
+
controller.enqueue({
|
|
617
|
+
type: "tool-input-end",
|
|
618
|
+
id: block.toolCallId
|
|
619
|
+
});
|
|
620
|
+
controller.enqueue({
|
|
621
|
+
type: "tool-call",
|
|
622
|
+
toolCallId: block.toolCallId,
|
|
623
|
+
toolName: block.toolName,
|
|
624
|
+
input: block.input === "" ? "{}" : block.input
|
|
625
|
+
});
|
|
626
|
+
}
|
|
627
|
+
}
|
|
579
628
|
if (!isRetry && self.isTokenError(error)) {
|
|
580
629
|
self.directAccessClient.invalidateToken();
|
|
581
630
|
controller.enqueue({
|
|
@@ -613,6 +662,740 @@ var GitLabAgenticLanguageModel = class {
|
|
|
613
662
|
}
|
|
614
663
|
};
|
|
615
664
|
|
|
665
|
+
// src/gitlab-openai-language-model.ts
|
|
666
|
+
var import_openai = __toESM(require("openai"));
|
|
667
|
+
|
|
668
|
+
// src/model-mappings.ts
|
|
669
|
+
var MODEL_MAPPINGS = {
|
|
670
|
+
// Anthropic models
|
|
671
|
+
"duo-chat-opus-4-5": { provider: "anthropic", model: "claude-opus-4-5-20251101" },
|
|
672
|
+
"duo-chat-sonnet-4-5": { provider: "anthropic", model: "claude-sonnet-4-5-20250929" },
|
|
673
|
+
"duo-chat-haiku-4-5": { provider: "anthropic", model: "claude-haiku-4-5-20251001" },
|
|
674
|
+
// OpenAI models - Chat Completions API
|
|
675
|
+
"duo-chat-gpt-5-1": { provider: "openai", model: "gpt-5.1-2025-11-13", openaiApiType: "chat" },
|
|
676
|
+
"duo-chat-gpt-5-mini": {
|
|
677
|
+
provider: "openai",
|
|
678
|
+
model: "gpt-5-mini-2025-08-07",
|
|
679
|
+
openaiApiType: "chat"
|
|
680
|
+
},
|
|
681
|
+
// OpenAI models - Responses API (Codex models)
|
|
682
|
+
"duo-chat-gpt-5-codex": { provider: "openai", model: "gpt-5-codex", openaiApiType: "responses" },
|
|
683
|
+
"duo-chat-gpt-5-2-codex": {
|
|
684
|
+
provider: "openai",
|
|
685
|
+
model: "gpt-5.2-codex",
|
|
686
|
+
openaiApiType: "responses"
|
|
687
|
+
}
|
|
688
|
+
};
|
|
689
|
+
function getModelMapping(modelId) {
|
|
690
|
+
return MODEL_MAPPINGS[modelId];
|
|
691
|
+
}
|
|
692
|
+
function getProviderForModelId(modelId) {
|
|
693
|
+
return MODEL_MAPPINGS[modelId]?.provider;
|
|
694
|
+
}
|
|
695
|
+
function getValidModelsForProvider(provider) {
|
|
696
|
+
return Object.values(MODEL_MAPPINGS).filter((m) => m.provider === provider).map((m) => m.model);
|
|
697
|
+
}
|
|
698
|
+
function getAnthropicModelForModelId(modelId) {
|
|
699
|
+
const mapping = MODEL_MAPPINGS[modelId];
|
|
700
|
+
return mapping?.provider === "anthropic" ? mapping.model : void 0;
|
|
701
|
+
}
|
|
702
|
+
function getOpenAIModelForModelId(modelId) {
|
|
703
|
+
const mapping = MODEL_MAPPINGS[modelId];
|
|
704
|
+
return mapping?.provider === "openai" ? mapping.model : void 0;
|
|
705
|
+
}
|
|
706
|
+
function getOpenAIApiType(modelId) {
|
|
707
|
+
const mapping = MODEL_MAPPINGS[modelId];
|
|
708
|
+
return mapping?.openaiApiType ?? "chat";
|
|
709
|
+
}
|
|
710
|
+
function isResponsesApiModel(modelId) {
|
|
711
|
+
return getOpenAIApiType(modelId) === "responses";
|
|
712
|
+
}
|
|
713
|
+
var MODEL_ID_TO_ANTHROPIC_MODEL = Object.fromEntries(
|
|
714
|
+
Object.entries(MODEL_MAPPINGS).filter(([, v]) => v.provider === "anthropic").map(([k, v]) => [k, v.model])
|
|
715
|
+
);
|
|
716
|
+
|
|
717
|
+
// src/gitlab-openai-language-model.ts
|
|
718
|
+
var GitLabOpenAILanguageModel = class {
|
|
719
|
+
specificationVersion = "v2";
|
|
720
|
+
modelId;
|
|
721
|
+
supportedUrls = {};
|
|
722
|
+
config;
|
|
723
|
+
directAccessClient;
|
|
724
|
+
useResponsesApi;
|
|
725
|
+
openaiClient = null;
|
|
726
|
+
constructor(modelId, config) {
|
|
727
|
+
this.modelId = modelId;
|
|
728
|
+
this.config = config;
|
|
729
|
+
this.useResponsesApi = config.useResponsesApi ?? isResponsesApiModel(modelId);
|
|
730
|
+
this.directAccessClient = new GitLabDirectAccessClient({
|
|
731
|
+
instanceUrl: config.instanceUrl,
|
|
732
|
+
getHeaders: config.getHeaders,
|
|
733
|
+
refreshApiKey: config.refreshApiKey,
|
|
734
|
+
fetch: config.fetch,
|
|
735
|
+
featureFlags: config.featureFlags,
|
|
736
|
+
aiGatewayUrl: config.aiGatewayUrl
|
|
737
|
+
});
|
|
738
|
+
}
|
|
739
|
+
get provider() {
|
|
740
|
+
return this.config.provider;
|
|
741
|
+
}
|
|
742
|
+
async getOpenAIClient(forceRefresh = false) {
|
|
743
|
+
const tokenData = await this.directAccessClient.getDirectAccessToken(forceRefresh);
|
|
744
|
+
const { "x-api-key": _removed, ...filteredHeaders } = tokenData.headers;
|
|
745
|
+
this.openaiClient = new import_openai.default({
|
|
746
|
+
apiKey: tokenData.token,
|
|
747
|
+
baseURL: this.directAccessClient.getOpenAIProxyUrl(),
|
|
748
|
+
defaultHeaders: filteredHeaders
|
|
749
|
+
});
|
|
750
|
+
return this.openaiClient;
|
|
751
|
+
}
|
|
752
|
+
isTokenError(error) {
|
|
753
|
+
if (error instanceof import_openai.default.APIError) {
|
|
754
|
+
if (error.status === 401) {
|
|
755
|
+
return true;
|
|
756
|
+
}
|
|
757
|
+
const message = error.message?.toLowerCase() || "";
|
|
758
|
+
if (message.includes("token") && (message.includes("expired") || message.includes("revoked") || message.includes("invalid"))) {
|
|
759
|
+
return true;
|
|
760
|
+
}
|
|
761
|
+
}
|
|
762
|
+
return false;
|
|
763
|
+
}
|
|
764
|
+
convertTools(tools) {
|
|
765
|
+
if (!tools || tools.length === 0) {
|
|
766
|
+
return void 0;
|
|
767
|
+
}
|
|
768
|
+
return tools.filter((tool) => tool.type === "function").map((tool) => {
|
|
769
|
+
const schema = tool.inputSchema;
|
|
770
|
+
return {
|
|
771
|
+
type: "function",
|
|
772
|
+
function: {
|
|
773
|
+
name: tool.name,
|
|
774
|
+
description: tool.description || "",
|
|
775
|
+
// Ensure the schema has type: 'object' as OpenAI requires it
|
|
776
|
+
parameters: {
|
|
777
|
+
type: "object",
|
|
778
|
+
...schema
|
|
779
|
+
}
|
|
780
|
+
}
|
|
781
|
+
};
|
|
782
|
+
});
|
|
783
|
+
}
|
|
784
|
+
convertToolChoice(toolChoice) {
|
|
785
|
+
if (!toolChoice) {
|
|
786
|
+
return void 0;
|
|
787
|
+
}
|
|
788
|
+
switch (toolChoice.type) {
|
|
789
|
+
case "auto":
|
|
790
|
+
return "auto";
|
|
791
|
+
case "none":
|
|
792
|
+
return "none";
|
|
793
|
+
case "required":
|
|
794
|
+
return "required";
|
|
795
|
+
case "tool":
|
|
796
|
+
return { type: "function", function: { name: toolChoice.toolName } };
|
|
797
|
+
default:
|
|
798
|
+
return void 0;
|
|
799
|
+
}
|
|
800
|
+
}
|
|
801
|
+
convertPrompt(prompt) {
|
|
802
|
+
const messages = [];
|
|
803
|
+
for (const message of prompt) {
|
|
804
|
+
if (message.role === "system") {
|
|
805
|
+
messages.push({ role: "system", content: message.content });
|
|
806
|
+
continue;
|
|
807
|
+
}
|
|
808
|
+
if (message.role === "user") {
|
|
809
|
+
const textParts = message.content.filter((part) => part.type === "text").map((part) => part.text);
|
|
810
|
+
if (textParts.length > 0) {
|
|
811
|
+
messages.push({ role: "user", content: textParts.join("\n") });
|
|
812
|
+
}
|
|
813
|
+
} else if (message.role === "assistant") {
|
|
814
|
+
const textParts = [];
|
|
815
|
+
const toolCalls = [];
|
|
816
|
+
for (const part of message.content) {
|
|
817
|
+
if (part.type === "text") {
|
|
818
|
+
textParts.push(part.text);
|
|
819
|
+
} else if (part.type === "tool-call") {
|
|
820
|
+
toolCalls.push({
|
|
821
|
+
id: part.toolCallId,
|
|
822
|
+
type: "function",
|
|
823
|
+
function: {
|
|
824
|
+
name: part.toolName,
|
|
825
|
+
arguments: typeof part.input === "string" ? part.input : JSON.stringify(part.input)
|
|
826
|
+
}
|
|
827
|
+
});
|
|
828
|
+
}
|
|
829
|
+
}
|
|
830
|
+
const assistantMessage = {
|
|
831
|
+
role: "assistant",
|
|
832
|
+
content: textParts.length > 0 ? textParts.join("\n") : null
|
|
833
|
+
};
|
|
834
|
+
if (toolCalls.length > 0) {
|
|
835
|
+
assistantMessage.tool_calls = toolCalls;
|
|
836
|
+
}
|
|
837
|
+
messages.push(assistantMessage);
|
|
838
|
+
} else if (message.role === "tool") {
|
|
839
|
+
for (const part of message.content) {
|
|
840
|
+
if (part.type === "tool-result") {
|
|
841
|
+
let resultContent;
|
|
842
|
+
if (part.output.type === "text") {
|
|
843
|
+
resultContent = part.output.value;
|
|
844
|
+
} else if (part.output.type === "json") {
|
|
845
|
+
resultContent = JSON.stringify(part.output.value);
|
|
846
|
+
} else if (part.output.type === "error-text") {
|
|
847
|
+
resultContent = part.output.value;
|
|
848
|
+
} else if (part.output.type === "error-json") {
|
|
849
|
+
resultContent = JSON.stringify(part.output.value);
|
|
850
|
+
} else {
|
|
851
|
+
resultContent = JSON.stringify(part.output);
|
|
852
|
+
}
|
|
853
|
+
messages.push({
|
|
854
|
+
role: "tool",
|
|
855
|
+
tool_call_id: part.toolCallId,
|
|
856
|
+
content: resultContent
|
|
857
|
+
});
|
|
858
|
+
}
|
|
859
|
+
}
|
|
860
|
+
}
|
|
861
|
+
}
|
|
862
|
+
return messages;
|
|
863
|
+
}
|
|
864
|
+
convertFinishReason(finishReason) {
|
|
865
|
+
switch (finishReason) {
|
|
866
|
+
case "stop":
|
|
867
|
+
return "stop";
|
|
868
|
+
case "length":
|
|
869
|
+
return "length";
|
|
870
|
+
case "tool_calls":
|
|
871
|
+
return "tool-calls";
|
|
872
|
+
case "content_filter":
|
|
873
|
+
return "content-filter";
|
|
874
|
+
default:
|
|
875
|
+
return "unknown";
|
|
876
|
+
}
|
|
877
|
+
}
|
|
878
|
+
/**
|
|
879
|
+
* Convert tools to Responses API format
|
|
880
|
+
*/
|
|
881
|
+
convertToolsForResponses(tools) {
|
|
882
|
+
if (!tools || tools.length === 0) {
|
|
883
|
+
return void 0;
|
|
884
|
+
}
|
|
885
|
+
return tools.filter((tool) => tool.type === "function").map((tool) => {
|
|
886
|
+
const schema = { ...tool.inputSchema };
|
|
887
|
+
delete schema["$schema"];
|
|
888
|
+
return {
|
|
889
|
+
type: "function",
|
|
890
|
+
name: tool.name,
|
|
891
|
+
description: tool.description || "",
|
|
892
|
+
parameters: schema,
|
|
893
|
+
strict: false
|
|
894
|
+
};
|
|
895
|
+
});
|
|
896
|
+
}
|
|
897
|
+
/**
|
|
898
|
+
* Convert prompt to Responses API input format
|
|
899
|
+
*/
|
|
900
|
+
convertPromptForResponses(prompt) {
|
|
901
|
+
const items = [];
|
|
902
|
+
for (const message of prompt) {
|
|
903
|
+
if (message.role === "system") {
|
|
904
|
+
continue;
|
|
905
|
+
}
|
|
906
|
+
if (message.role === "user") {
|
|
907
|
+
const textParts = message.content.filter((part) => part.type === "text").map((part) => part.text);
|
|
908
|
+
if (textParts.length > 0) {
|
|
909
|
+
items.push({
|
|
910
|
+
type: "message",
|
|
911
|
+
role: "user",
|
|
912
|
+
content: textParts.map((text) => ({ type: "input_text", text }))
|
|
913
|
+
});
|
|
914
|
+
}
|
|
915
|
+
} else if (message.role === "assistant") {
|
|
916
|
+
const textParts = [];
|
|
917
|
+
for (const part of message.content) {
|
|
918
|
+
if (part.type === "text") {
|
|
919
|
+
textParts.push(part.text);
|
|
920
|
+
} else if (part.type === "tool-call") {
|
|
921
|
+
items.push({
|
|
922
|
+
type: "function_call",
|
|
923
|
+
call_id: part.toolCallId,
|
|
924
|
+
name: part.toolName,
|
|
925
|
+
arguments: typeof part.input === "string" ? part.input : JSON.stringify(part.input)
|
|
926
|
+
});
|
|
927
|
+
}
|
|
928
|
+
}
|
|
929
|
+
if (textParts.length > 0) {
|
|
930
|
+
items.push({
|
|
931
|
+
type: "message",
|
|
932
|
+
role: "assistant",
|
|
933
|
+
content: [{ type: "output_text", text: textParts.join("\n"), annotations: [] }]
|
|
934
|
+
});
|
|
935
|
+
}
|
|
936
|
+
} else if (message.role === "tool") {
|
|
937
|
+
for (const part of message.content) {
|
|
938
|
+
if (part.type === "tool-result") {
|
|
939
|
+
let resultContent;
|
|
940
|
+
if (part.output.type === "text") {
|
|
941
|
+
resultContent = part.output.value;
|
|
942
|
+
} else if (part.output.type === "json") {
|
|
943
|
+
resultContent = JSON.stringify(part.output.value);
|
|
944
|
+
} else if (part.output.type === "error-text") {
|
|
945
|
+
resultContent = part.output.value;
|
|
946
|
+
} else if (part.output.type === "error-json") {
|
|
947
|
+
resultContent = JSON.stringify(part.output.value);
|
|
948
|
+
} else {
|
|
949
|
+
resultContent = JSON.stringify(part.output);
|
|
950
|
+
}
|
|
951
|
+
items.push({
|
|
952
|
+
type: "function_call_output",
|
|
953
|
+
call_id: part.toolCallId,
|
|
954
|
+
output: resultContent
|
|
955
|
+
});
|
|
956
|
+
}
|
|
957
|
+
}
|
|
958
|
+
}
|
|
959
|
+
}
|
|
960
|
+
return items;
|
|
961
|
+
}
|
|
962
|
+
/**
|
|
963
|
+
* Extract system instructions from prompt
|
|
964
|
+
*/
|
|
965
|
+
extractSystemInstructions(prompt) {
|
|
966
|
+
const systemMessages = prompt.filter((m) => m.role === "system").map((m) => m.content).join("\n");
|
|
967
|
+
return systemMessages || void 0;
|
|
968
|
+
}
|
|
969
|
+
/**
|
|
970
|
+
* Convert Responses API status to finish reason
|
|
971
|
+
* Note: Responses API returns 'completed' even when making tool calls,
|
|
972
|
+
* so we need to check the content for tool calls separately.
|
|
973
|
+
*/
|
|
974
|
+
convertResponsesStatus(status, hasToolCalls = false) {
|
|
975
|
+
if (hasToolCalls) {
|
|
976
|
+
return "tool-calls";
|
|
977
|
+
}
|
|
978
|
+
switch (status) {
|
|
979
|
+
case "completed":
|
|
980
|
+
return "stop";
|
|
981
|
+
case "incomplete":
|
|
982
|
+
return "length";
|
|
983
|
+
case "cancelled":
|
|
984
|
+
return "stop";
|
|
985
|
+
case "failed":
|
|
986
|
+
return "error";
|
|
987
|
+
default:
|
|
988
|
+
return "unknown";
|
|
989
|
+
}
|
|
990
|
+
}
|
|
991
|
+
async doGenerate(options) {
|
|
992
|
+
if (this.useResponsesApi) {
|
|
993
|
+
return this.doGenerateWithResponsesApi(options, false);
|
|
994
|
+
}
|
|
995
|
+
return this.doGenerateWithChatApi(options, false);
|
|
996
|
+
}
|
|
997
|
+
async doGenerateWithChatApi(options, isRetry) {
|
|
998
|
+
const client = await this.getOpenAIClient(isRetry);
|
|
999
|
+
const messages = this.convertPrompt(options.prompt);
|
|
1000
|
+
const tools = this.convertTools(options.tools);
|
|
1001
|
+
const toolChoice = options.toolChoice?.type !== "none" ? this.convertToolChoice(options.toolChoice) : void 0;
|
|
1002
|
+
const openaiModel = this.config.openaiModel || "gpt-4o";
|
|
1003
|
+
const maxTokens = options.maxOutputTokens || this.config.maxTokens || 8192;
|
|
1004
|
+
try {
|
|
1005
|
+
const response = await client.chat.completions.create({
|
|
1006
|
+
model: openaiModel,
|
|
1007
|
+
max_completion_tokens: maxTokens,
|
|
1008
|
+
messages,
|
|
1009
|
+
tools,
|
|
1010
|
+
tool_choice: tools ? toolChoice : void 0,
|
|
1011
|
+
temperature: options.temperature,
|
|
1012
|
+
top_p: options.topP,
|
|
1013
|
+
stop: options.stopSequences
|
|
1014
|
+
});
|
|
1015
|
+
const choice = response.choices[0];
|
|
1016
|
+
const content = [];
|
|
1017
|
+
if (choice?.message.content) {
|
|
1018
|
+
content.push({ type: "text", text: choice.message.content });
|
|
1019
|
+
}
|
|
1020
|
+
if (choice?.message.tool_calls) {
|
|
1021
|
+
for (const toolCall of choice.message.tool_calls) {
|
|
1022
|
+
if (toolCall.type === "function") {
|
|
1023
|
+
content.push({
|
|
1024
|
+
type: "tool-call",
|
|
1025
|
+
toolCallId: toolCall.id,
|
|
1026
|
+
toolName: toolCall.function.name,
|
|
1027
|
+
input: toolCall.function.arguments
|
|
1028
|
+
});
|
|
1029
|
+
}
|
|
1030
|
+
}
|
|
1031
|
+
}
|
|
1032
|
+
const usage = {
|
|
1033
|
+
inputTokens: response.usage?.prompt_tokens || 0,
|
|
1034
|
+
outputTokens: response.usage?.completion_tokens || 0,
|
|
1035
|
+
totalTokens: response.usage?.total_tokens || 0
|
|
1036
|
+
};
|
|
1037
|
+
return {
|
|
1038
|
+
content,
|
|
1039
|
+
finishReason: this.convertFinishReason(choice?.finish_reason),
|
|
1040
|
+
usage,
|
|
1041
|
+
warnings: []
|
|
1042
|
+
};
|
|
1043
|
+
} catch (error) {
|
|
1044
|
+
if (!isRetry && this.isTokenError(error)) {
|
|
1045
|
+
this.directAccessClient.invalidateToken();
|
|
1046
|
+
return this.doGenerateWithChatApi(options, true);
|
|
1047
|
+
}
|
|
1048
|
+
if (error instanceof import_openai.default.APIError) {
|
|
1049
|
+
throw new GitLabError({
|
|
1050
|
+
message: `OpenAI API error: ${error.message}`,
|
|
1051
|
+
cause: error
|
|
1052
|
+
});
|
|
1053
|
+
}
|
|
1054
|
+
throw error;
|
|
1055
|
+
}
|
|
1056
|
+
}
|
|
1057
|
+
async doGenerateWithResponsesApi(options, isRetry) {
|
|
1058
|
+
const client = await this.getOpenAIClient(isRetry);
|
|
1059
|
+
const input = this.convertPromptForResponses(options.prompt);
|
|
1060
|
+
const tools = this.convertToolsForResponses(options.tools);
|
|
1061
|
+
const instructions = this.extractSystemInstructions(options.prompt);
|
|
1062
|
+
const openaiModel = this.config.openaiModel || "gpt-5-codex";
|
|
1063
|
+
const maxTokens = options.maxOutputTokens || this.config.maxTokens || 8192;
|
|
1064
|
+
try {
|
|
1065
|
+
const response = await client.responses.create({
|
|
1066
|
+
model: openaiModel,
|
|
1067
|
+
input,
|
|
1068
|
+
instructions,
|
|
1069
|
+
tools,
|
|
1070
|
+
max_output_tokens: maxTokens,
|
|
1071
|
+
temperature: options.temperature,
|
|
1072
|
+
top_p: options.topP,
|
|
1073
|
+
store: false
|
|
1074
|
+
});
|
|
1075
|
+
const content = [];
|
|
1076
|
+
let hasToolCalls = false;
|
|
1077
|
+
for (const item of response.output || []) {
|
|
1078
|
+
if (item.type === "message" && item.role === "assistant") {
|
|
1079
|
+
for (const contentItem of item.content || []) {
|
|
1080
|
+
if (contentItem.type === "output_text") {
|
|
1081
|
+
content.push({ type: "text", text: contentItem.text });
|
|
1082
|
+
}
|
|
1083
|
+
}
|
|
1084
|
+
} else if (item.type === "function_call") {
|
|
1085
|
+
hasToolCalls = true;
|
|
1086
|
+
content.push({
|
|
1087
|
+
type: "tool-call",
|
|
1088
|
+
toolCallId: item.call_id,
|
|
1089
|
+
toolName: item.name,
|
|
1090
|
+
input: item.arguments
|
|
1091
|
+
});
|
|
1092
|
+
}
|
|
1093
|
+
}
|
|
1094
|
+
const usage = {
|
|
1095
|
+
inputTokens: response.usage?.input_tokens || 0,
|
|
1096
|
+
outputTokens: response.usage?.output_tokens || 0,
|
|
1097
|
+
totalTokens: response.usage?.total_tokens || 0
|
|
1098
|
+
};
|
|
1099
|
+
return {
|
|
1100
|
+
content,
|
|
1101
|
+
finishReason: this.convertResponsesStatus(response.status, hasToolCalls),
|
|
1102
|
+
usage,
|
|
1103
|
+
warnings: []
|
|
1104
|
+
};
|
|
1105
|
+
} catch (error) {
|
|
1106
|
+
if (!isRetry && this.isTokenError(error)) {
|
|
1107
|
+
this.directAccessClient.invalidateToken();
|
|
1108
|
+
return this.doGenerateWithResponsesApi(options, true);
|
|
1109
|
+
}
|
|
1110
|
+
if (error instanceof import_openai.default.APIError) {
|
|
1111
|
+
throw new GitLabError({
|
|
1112
|
+
message: `OpenAI API error: ${error.message}`,
|
|
1113
|
+
cause: error
|
|
1114
|
+
});
|
|
1115
|
+
}
|
|
1116
|
+
throw error;
|
|
1117
|
+
}
|
|
1118
|
+
}
|
|
1119
|
+
async doStream(options) {
|
|
1120
|
+
if (this.useResponsesApi) {
|
|
1121
|
+
return this.doStreamWithResponsesApi(options, false);
|
|
1122
|
+
}
|
|
1123
|
+
return this.doStreamWithChatApi(options, false);
|
|
1124
|
+
}
|
|
1125
|
+
async doStreamWithChatApi(options, isRetry) {
|
|
1126
|
+
const client = await this.getOpenAIClient(isRetry);
|
|
1127
|
+
const messages = this.convertPrompt(options.prompt);
|
|
1128
|
+
const tools = this.convertTools(options.tools);
|
|
1129
|
+
const toolChoice = options.toolChoice?.type !== "none" ? this.convertToolChoice(options.toolChoice) : void 0;
|
|
1130
|
+
const openaiModel = this.config.openaiModel || "gpt-4o";
|
|
1131
|
+
const maxTokens = options.maxOutputTokens || this.config.maxTokens || 8192;
|
|
1132
|
+
const requestBody = {
|
|
1133
|
+
model: openaiModel,
|
|
1134
|
+
max_completion_tokens: maxTokens,
|
|
1135
|
+
messages,
|
|
1136
|
+
tools,
|
|
1137
|
+
tool_choice: tools ? toolChoice : void 0,
|
|
1138
|
+
temperature: options.temperature,
|
|
1139
|
+
top_p: options.topP,
|
|
1140
|
+
stop: options.stopSequences,
|
|
1141
|
+
stream: true,
|
|
1142
|
+
stream_options: { include_usage: true }
|
|
1143
|
+
};
|
|
1144
|
+
const self = this;
|
|
1145
|
+
const stream = new ReadableStream({
|
|
1146
|
+
start: async (controller) => {
|
|
1147
|
+
const toolCalls = {};
|
|
1148
|
+
const usage = {
|
|
1149
|
+
inputTokens: 0,
|
|
1150
|
+
outputTokens: 0,
|
|
1151
|
+
totalTokens: 0
|
|
1152
|
+
};
|
|
1153
|
+
let finishReason = "unknown";
|
|
1154
|
+
let textStarted = false;
|
|
1155
|
+
const textId = "text-0";
|
|
1156
|
+
try {
|
|
1157
|
+
const openaiStream = await client.chat.completions.create({
|
|
1158
|
+
...requestBody,
|
|
1159
|
+
stream: true
|
|
1160
|
+
});
|
|
1161
|
+
controller.enqueue({ type: "stream-start", warnings: [] });
|
|
1162
|
+
for await (const chunk of openaiStream) {
|
|
1163
|
+
const choice = chunk.choices?.[0];
|
|
1164
|
+
if (chunk.id && !textStarted) {
|
|
1165
|
+
controller.enqueue({
|
|
1166
|
+
type: "response-metadata",
|
|
1167
|
+
id: chunk.id,
|
|
1168
|
+
modelId: chunk.model
|
|
1169
|
+
});
|
|
1170
|
+
}
|
|
1171
|
+
if (choice?.delta?.content) {
|
|
1172
|
+
if (!textStarted) {
|
|
1173
|
+
controller.enqueue({ type: "text-start", id: textId });
|
|
1174
|
+
textStarted = true;
|
|
1175
|
+
}
|
|
1176
|
+
controller.enqueue({
|
|
1177
|
+
type: "text-delta",
|
|
1178
|
+
id: textId,
|
|
1179
|
+
delta: choice.delta.content
|
|
1180
|
+
});
|
|
1181
|
+
}
|
|
1182
|
+
if (choice?.delta?.tool_calls) {
|
|
1183
|
+
for (const tc of choice.delta.tool_calls) {
|
|
1184
|
+
const idx = tc.index;
|
|
1185
|
+
if (!toolCalls[idx]) {
|
|
1186
|
+
toolCalls[idx] = {
|
|
1187
|
+
id: tc.id || "",
|
|
1188
|
+
name: tc.function?.name || "",
|
|
1189
|
+
arguments: ""
|
|
1190
|
+
};
|
|
1191
|
+
controller.enqueue({
|
|
1192
|
+
type: "tool-input-start",
|
|
1193
|
+
id: toolCalls[idx].id,
|
|
1194
|
+
toolName: toolCalls[idx].name
|
|
1195
|
+
});
|
|
1196
|
+
}
|
|
1197
|
+
if (tc.function?.arguments) {
|
|
1198
|
+
toolCalls[idx].arguments += tc.function.arguments;
|
|
1199
|
+
controller.enqueue({
|
|
1200
|
+
type: "tool-input-delta",
|
|
1201
|
+
id: toolCalls[idx].id,
|
|
1202
|
+
delta: tc.function.arguments
|
|
1203
|
+
});
|
|
1204
|
+
}
|
|
1205
|
+
}
|
|
1206
|
+
}
|
|
1207
|
+
if (choice?.finish_reason) {
|
|
1208
|
+
finishReason = self.convertFinishReason(choice.finish_reason);
|
|
1209
|
+
}
|
|
1210
|
+
if (chunk.usage) {
|
|
1211
|
+
usage.inputTokens = chunk.usage.prompt_tokens || 0;
|
|
1212
|
+
usage.outputTokens = chunk.usage.completion_tokens || 0;
|
|
1213
|
+
usage.totalTokens = chunk.usage.total_tokens || 0;
|
|
1214
|
+
}
|
|
1215
|
+
}
|
|
1216
|
+
if (textStarted) {
|
|
1217
|
+
controller.enqueue({ type: "text-end", id: textId });
|
|
1218
|
+
}
|
|
1219
|
+
for (const [, tc] of Object.entries(toolCalls)) {
|
|
1220
|
+
controller.enqueue({ type: "tool-input-end", id: tc.id });
|
|
1221
|
+
controller.enqueue({
|
|
1222
|
+
type: "tool-call",
|
|
1223
|
+
toolCallId: tc.id,
|
|
1224
|
+
toolName: tc.name,
|
|
1225
|
+
input: tc.arguments || "{}"
|
|
1226
|
+
});
|
|
1227
|
+
}
|
|
1228
|
+
controller.enqueue({ type: "finish", finishReason, usage });
|
|
1229
|
+
controller.close();
|
|
1230
|
+
} catch (error) {
|
|
1231
|
+
if (!isRetry && self.isTokenError(error)) {
|
|
1232
|
+
self.directAccessClient.invalidateToken();
|
|
1233
|
+
controller.enqueue({
|
|
1234
|
+
type: "error",
|
|
1235
|
+
error: new GitLabError({ message: "TOKEN_REFRESH_NEEDED", cause: error })
|
|
1236
|
+
});
|
|
1237
|
+
controller.close();
|
|
1238
|
+
return;
|
|
1239
|
+
}
|
|
1240
|
+
if (error instanceof import_openai.default.APIError) {
|
|
1241
|
+
controller.enqueue({
|
|
1242
|
+
type: "error",
|
|
1243
|
+
error: new GitLabError({
|
|
1244
|
+
message: `OpenAI API error: ${error.message}`,
|
|
1245
|
+
cause: error
|
|
1246
|
+
})
|
|
1247
|
+
});
|
|
1248
|
+
} else {
|
|
1249
|
+
controller.enqueue({ type: "error", error });
|
|
1250
|
+
}
|
|
1251
|
+
controller.close();
|
|
1252
|
+
}
|
|
1253
|
+
}
|
|
1254
|
+
});
|
|
1255
|
+
return { stream, request: { body: requestBody } };
|
|
1256
|
+
}
|
|
1257
|
+
async doStreamWithResponsesApi(options, isRetry) {
|
|
1258
|
+
const client = await this.getOpenAIClient(isRetry);
|
|
1259
|
+
const input = this.convertPromptForResponses(options.prompt);
|
|
1260
|
+
const tools = this.convertToolsForResponses(options.tools);
|
|
1261
|
+
const instructions = this.extractSystemInstructions(options.prompt);
|
|
1262
|
+
const openaiModel = this.config.openaiModel || "gpt-5-codex";
|
|
1263
|
+
const maxTokens = options.maxOutputTokens || this.config.maxTokens || 8192;
|
|
1264
|
+
const requestBody = {
|
|
1265
|
+
model: openaiModel,
|
|
1266
|
+
input,
|
|
1267
|
+
instructions,
|
|
1268
|
+
tools,
|
|
1269
|
+
max_output_tokens: maxTokens,
|
|
1270
|
+
temperature: options.temperature,
|
|
1271
|
+
top_p: options.topP,
|
|
1272
|
+
store: false,
|
|
1273
|
+
stream: true
|
|
1274
|
+
};
|
|
1275
|
+
const self = this;
|
|
1276
|
+
const stream = new ReadableStream({
|
|
1277
|
+
start: async (controller) => {
|
|
1278
|
+
const toolCalls = {};
|
|
1279
|
+
const usage = {
|
|
1280
|
+
inputTokens: 0,
|
|
1281
|
+
outputTokens: 0,
|
|
1282
|
+
totalTokens: 0
|
|
1283
|
+
};
|
|
1284
|
+
let finishReason = "unknown";
|
|
1285
|
+
let textStarted = false;
|
|
1286
|
+
const textId = "text-0";
|
|
1287
|
+
try {
|
|
1288
|
+
const openaiStream = await client.responses.create({
|
|
1289
|
+
...requestBody,
|
|
1290
|
+
stream: true
|
|
1291
|
+
});
|
|
1292
|
+
controller.enqueue({ type: "stream-start", warnings: [] });
|
|
1293
|
+
for await (const event of openaiStream) {
|
|
1294
|
+
if (event.type === "response.created") {
|
|
1295
|
+
controller.enqueue({
|
|
1296
|
+
type: "response-metadata",
|
|
1297
|
+
id: event.response.id,
|
|
1298
|
+
modelId: event.response.model
|
|
1299
|
+
});
|
|
1300
|
+
} else if (event.type === "response.output_item.added") {
|
|
1301
|
+
if (event.item.type === "function_call") {
|
|
1302
|
+
const outputIndex = event.output_index;
|
|
1303
|
+
const callId = event.item.call_id;
|
|
1304
|
+
toolCalls[outputIndex] = {
|
|
1305
|
+
callId,
|
|
1306
|
+
name: event.item.name,
|
|
1307
|
+
arguments: ""
|
|
1308
|
+
};
|
|
1309
|
+
controller.enqueue({
|
|
1310
|
+
type: "tool-input-start",
|
|
1311
|
+
id: callId,
|
|
1312
|
+
toolName: event.item.name
|
|
1313
|
+
});
|
|
1314
|
+
}
|
|
1315
|
+
} else if (event.type === "response.output_text.delta") {
|
|
1316
|
+
if (!textStarted) {
|
|
1317
|
+
controller.enqueue({ type: "text-start", id: textId });
|
|
1318
|
+
textStarted = true;
|
|
1319
|
+
}
|
|
1320
|
+
controller.enqueue({
|
|
1321
|
+
type: "text-delta",
|
|
1322
|
+
id: textId,
|
|
1323
|
+
delta: event.delta
|
|
1324
|
+
});
|
|
1325
|
+
} else if (event.type === "response.function_call_arguments.delta") {
|
|
1326
|
+
const outputIndex = event.output_index;
|
|
1327
|
+
const tc = toolCalls[outputIndex];
|
|
1328
|
+
if (tc) {
|
|
1329
|
+
tc.arguments += event.delta;
|
|
1330
|
+
controller.enqueue({
|
|
1331
|
+
type: "tool-input-delta",
|
|
1332
|
+
id: tc.callId,
|
|
1333
|
+
delta: event.delta
|
|
1334
|
+
});
|
|
1335
|
+
}
|
|
1336
|
+
} else if (event.type === "response.function_call_arguments.done") {
|
|
1337
|
+
const outputIndex = event.output_index;
|
|
1338
|
+
const tc = toolCalls[outputIndex];
|
|
1339
|
+
if (tc) {
|
|
1340
|
+
tc.arguments = event.arguments;
|
|
1341
|
+
}
|
|
1342
|
+
} else if (event.type === "response.completed") {
|
|
1343
|
+
const hasToolCalls2 = Object.keys(toolCalls).length > 0;
|
|
1344
|
+
finishReason = self.convertResponsesStatus(event.response.status, hasToolCalls2);
|
|
1345
|
+
if (event.response.usage) {
|
|
1346
|
+
usage.inputTokens = event.response.usage.input_tokens || 0;
|
|
1347
|
+
usage.outputTokens = event.response.usage.output_tokens || 0;
|
|
1348
|
+
usage.totalTokens = event.response.usage.total_tokens || 0;
|
|
1349
|
+
}
|
|
1350
|
+
}
|
|
1351
|
+
}
|
|
1352
|
+
if (textStarted) {
|
|
1353
|
+
controller.enqueue({ type: "text-end", id: textId });
|
|
1354
|
+
}
|
|
1355
|
+
const hasToolCalls = Object.keys(toolCalls).length > 0;
|
|
1356
|
+
if (hasToolCalls && finishReason === "stop") {
|
|
1357
|
+
finishReason = "tool-calls";
|
|
1358
|
+
}
|
|
1359
|
+
for (const tc of Object.values(toolCalls)) {
|
|
1360
|
+
controller.enqueue({ type: "tool-input-end", id: tc.callId });
|
|
1361
|
+
controller.enqueue({
|
|
1362
|
+
type: "tool-call",
|
|
1363
|
+
toolCallId: tc.callId,
|
|
1364
|
+
toolName: tc.name,
|
|
1365
|
+
input: tc.arguments || "{}"
|
|
1366
|
+
});
|
|
1367
|
+
}
|
|
1368
|
+
controller.enqueue({ type: "finish", finishReason, usage });
|
|
1369
|
+
controller.close();
|
|
1370
|
+
} catch (error) {
|
|
1371
|
+
if (!isRetry && self.isTokenError(error)) {
|
|
1372
|
+
self.directAccessClient.invalidateToken();
|
|
1373
|
+
controller.enqueue({
|
|
1374
|
+
type: "error",
|
|
1375
|
+
error: new GitLabError({ message: "TOKEN_REFRESH_NEEDED", cause: error })
|
|
1376
|
+
});
|
|
1377
|
+
controller.close();
|
|
1378
|
+
return;
|
|
1379
|
+
}
|
|
1380
|
+
if (error instanceof import_openai.default.APIError) {
|
|
1381
|
+
controller.enqueue({
|
|
1382
|
+
type: "error",
|
|
1383
|
+
error: new GitLabError({
|
|
1384
|
+
message: `OpenAI API error: ${error.message}`,
|
|
1385
|
+
cause: error
|
|
1386
|
+
})
|
|
1387
|
+
});
|
|
1388
|
+
} else {
|
|
1389
|
+
controller.enqueue({ type: "error", error });
|
|
1390
|
+
}
|
|
1391
|
+
controller.close();
|
|
1392
|
+
}
|
|
1393
|
+
}
|
|
1394
|
+
});
|
|
1395
|
+
return { stream, request: { body: requestBody } };
|
|
1396
|
+
}
|
|
1397
|
+
};
|
|
1398
|
+
|
|
616
1399
|
// src/gitlab-oauth-types.ts
|
|
617
1400
|
var BUNDLED_CLIENT_ID = "36f2a70cddeb5a0889d4fd8295c241b7e9848e89cf9e599d0eed2d8e5350fbf5";
|
|
618
1401
|
var GITLAB_COM_URL = "https://gitlab.com";
|
|
@@ -775,16 +1558,6 @@ var GitLabOAuthManager = class {
|
|
|
775
1558
|
}
|
|
776
1559
|
};
|
|
777
1560
|
|
|
778
|
-
// src/model-mappings.ts
|
|
779
|
-
var MODEL_ID_TO_ANTHROPIC_MODEL = {
|
|
780
|
-
"duo-chat-opus-4-5": "claude-opus-4-5-20251101",
|
|
781
|
-
"duo-chat-sonnet-4-5": "claude-sonnet-4-5-20250929",
|
|
782
|
-
"duo-chat-haiku-4-5": "claude-haiku-4-5-20251001"
|
|
783
|
-
};
|
|
784
|
-
function getAnthropicModelForModelId(modelId) {
|
|
785
|
-
return MODEL_ID_TO_ANTHROPIC_MODEL[modelId];
|
|
786
|
-
}
|
|
787
|
-
|
|
788
1561
|
// src/gitlab-provider.ts
|
|
789
1562
|
var fs = __toESM(require("fs"));
|
|
790
1563
|
var path = __toESM(require("path"));
|
|
@@ -920,21 +1693,44 @@ function createGitLab(options = {}) {
|
|
|
920
1693
|
getApiKey().catch(() => {
|
|
921
1694
|
});
|
|
922
1695
|
const createAgenticChatModel = (modelId, agenticOptions) => {
|
|
1696
|
+
const mapping = getModelMapping(modelId);
|
|
1697
|
+
if (!mapping) {
|
|
1698
|
+
throw new GitLabError({
|
|
1699
|
+
message: `Unknown model ID: ${modelId}. Model must be registered in MODEL_MAPPINGS.`
|
|
1700
|
+
});
|
|
1701
|
+
}
|
|
1702
|
+
if (agenticOptions?.providerModel) {
|
|
1703
|
+
const validModels = getValidModelsForProvider(mapping.provider);
|
|
1704
|
+
if (!validModels.includes(agenticOptions.providerModel)) {
|
|
1705
|
+
throw new GitLabError({
|
|
1706
|
+
message: `Invalid providerModel '${agenticOptions.providerModel}' for provider '${mapping.provider}'. Valid models: ${validModels.join(", ")}`
|
|
1707
|
+
});
|
|
1708
|
+
}
|
|
1709
|
+
}
|
|
923
1710
|
const featureFlags = {
|
|
924
1711
|
DuoAgentPlatformNext: true,
|
|
925
1712
|
...options.featureFlags,
|
|
926
1713
|
...agenticOptions?.featureFlags
|
|
927
1714
|
};
|
|
928
|
-
|
|
1715
|
+
const baseConfig = {
|
|
929
1716
|
provider: `${providerName}.agentic`,
|
|
930
1717
|
instanceUrl,
|
|
931
1718
|
getHeaders,
|
|
932
1719
|
refreshApiKey,
|
|
933
1720
|
fetch: options.fetch,
|
|
934
|
-
anthropicModel: agenticOptions?.anthropicModel ?? getAnthropicModelForModelId(modelId),
|
|
935
1721
|
maxTokens: agenticOptions?.maxTokens,
|
|
936
1722
|
featureFlags,
|
|
937
1723
|
aiGatewayUrl: options.aiGatewayUrl
|
|
1724
|
+
};
|
|
1725
|
+
if (mapping.provider === "openai") {
|
|
1726
|
+
return new GitLabOpenAILanguageModel(modelId, {
|
|
1727
|
+
...baseConfig,
|
|
1728
|
+
openaiModel: agenticOptions?.providerModel ?? mapping.model
|
|
1729
|
+
});
|
|
1730
|
+
}
|
|
1731
|
+
return new GitLabAnthropicLanguageModel(modelId, {
|
|
1732
|
+
...baseConfig,
|
|
1733
|
+
anthropicModel: agenticOptions?.providerModel ?? mapping.model
|
|
938
1734
|
});
|
|
939
1735
|
};
|
|
940
1736
|
const createDefaultModel = (modelId) => {
|
|
@@ -1045,7 +1841,7 @@ var GitLabProjectCache = class {
|
|
|
1045
1841
|
};
|
|
1046
1842
|
|
|
1047
1843
|
// src/gitlab-project-detector.ts
|
|
1048
|
-
var
|
|
1844
|
+
var debugLog = (..._args) => {
|
|
1049
1845
|
};
|
|
1050
1846
|
var GitLabProjectDetector = class {
|
|
1051
1847
|
config;
|
|
@@ -1074,35 +1870,35 @@ var GitLabProjectDetector = class {
|
|
|
1074
1870
|
return cached;
|
|
1075
1871
|
}
|
|
1076
1872
|
try {
|
|
1077
|
-
|
|
1873
|
+
debugLog(`[GitLabProjectDetector] Getting git remote URL from: ${workingDirectory}`);
|
|
1078
1874
|
const remoteUrl = await this.getGitRemoteUrl(workingDirectory, remoteName);
|
|
1079
1875
|
if (!remoteUrl) {
|
|
1080
|
-
|
|
1876
|
+
debugLog(`[GitLabProjectDetector] No git remote URL found`);
|
|
1081
1877
|
return null;
|
|
1082
1878
|
}
|
|
1083
|
-
|
|
1084
|
-
|
|
1879
|
+
debugLog(`[GitLabProjectDetector] Git remote URL: ${remoteUrl}`);
|
|
1880
|
+
debugLog(
|
|
1085
1881
|
`[GitLabProjectDetector] Parsing project path from URL (instance: ${this.config.instanceUrl})`
|
|
1086
1882
|
);
|
|
1087
1883
|
const projectPath = this.parseGitRemoteUrl(remoteUrl, this.config.instanceUrl);
|
|
1088
1884
|
if (!projectPath) {
|
|
1089
|
-
|
|
1885
|
+
debugLog(
|
|
1090
1886
|
`[GitLabProjectDetector] Could not parse project path from URL (remote doesn't match instance)`
|
|
1091
1887
|
);
|
|
1092
1888
|
return null;
|
|
1093
1889
|
}
|
|
1094
|
-
|
|
1095
|
-
|
|
1890
|
+
debugLog(`[GitLabProjectDetector] Parsed project path: ${projectPath}`);
|
|
1891
|
+
debugLog(`[GitLabProjectDetector] Fetching project from GitLab API: ${projectPath}`);
|
|
1096
1892
|
const project = await this.getProjectByPath(projectPath);
|
|
1097
|
-
|
|
1893
|
+
debugLog(`[GitLabProjectDetector] \u2713 Project fetched successfully:`, project);
|
|
1098
1894
|
this.cache.set(cacheKey, project);
|
|
1099
1895
|
return project;
|
|
1100
1896
|
} catch (error) {
|
|
1101
1897
|
if (error instanceof GitLabError) {
|
|
1102
|
-
|
|
1898
|
+
debugLog(`[GitLabProjectDetector] GitLab API error:`, error.message || error);
|
|
1103
1899
|
return null;
|
|
1104
1900
|
}
|
|
1105
|
-
|
|
1901
|
+
debugLog(`[GitLabProjectDetector] Unexpected error:`, error);
|
|
1106
1902
|
console.warn(`Failed to auto-detect GitLab project: ${error}`);
|
|
1107
1903
|
return null;
|
|
1108
1904
|
}
|
|
@@ -1234,17 +2030,25 @@ var GitLabProjectDetector = class {
|
|
|
1234
2030
|
BUNDLED_CLIENT_ID,
|
|
1235
2031
|
DEFAULT_AI_GATEWAY_URL,
|
|
1236
2032
|
GITLAB_COM_URL,
|
|
1237
|
-
|
|
2033
|
+
GitLabAnthropicLanguageModel,
|
|
1238
2034
|
GitLabDirectAccessClient,
|
|
1239
2035
|
GitLabError,
|
|
1240
2036
|
GitLabOAuthManager,
|
|
2037
|
+
GitLabOpenAILanguageModel,
|
|
1241
2038
|
GitLabProjectCache,
|
|
1242
2039
|
GitLabProjectDetector,
|
|
1243
2040
|
MODEL_ID_TO_ANTHROPIC_MODEL,
|
|
2041
|
+
MODEL_MAPPINGS,
|
|
1244
2042
|
OAUTH_SCOPES,
|
|
1245
2043
|
TOKEN_EXPIRY_SKEW_MS,
|
|
1246
2044
|
createGitLab,
|
|
1247
2045
|
getAnthropicModelForModelId,
|
|
1248
|
-
|
|
2046
|
+
getModelMapping,
|
|
2047
|
+
getOpenAIApiType,
|
|
2048
|
+
getOpenAIModelForModelId,
|
|
2049
|
+
getProviderForModelId,
|
|
2050
|
+
getValidModelsForProvider,
|
|
2051
|
+
gitlab,
|
|
2052
|
+
isResponsesApiModel
|
|
1249
2053
|
});
|
|
1250
2054
|
//# sourceMappingURL=index.js.map
|