@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.mjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
// src/gitlab-
|
|
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,10 +151,8 @@ var GitLabDirectAccessClient = class {
|
|
|
142
151
|
}
|
|
143
152
|
};
|
|
144
153
|
|
|
145
|
-
// src/gitlab-
|
|
146
|
-
var
|
|
147
|
-
};
|
|
148
|
-
var GitLabAgenticLanguageModel = class {
|
|
154
|
+
// src/gitlab-anthropic-language-model.ts
|
|
155
|
+
var GitLabAnthropicLanguageModel = class {
|
|
149
156
|
specificationVersion = "v2";
|
|
150
157
|
modelId;
|
|
151
158
|
supportedUrls = {};
|
|
@@ -173,14 +180,7 @@ var GitLabAgenticLanguageModel = class {
|
|
|
173
180
|
*/
|
|
174
181
|
async getAnthropicClient(forceRefresh = false) {
|
|
175
182
|
const tokenData = await this.directAccessClient.getDirectAccessToken(forceRefresh);
|
|
176
|
-
debugLog("[gitlab-ai-provider] Token headers from GitLab:", tokenData.headers);
|
|
177
|
-
debugLog("[gitlab-ai-provider] Proxy URL:", this.directAccessClient.getAnthropicProxyUrl());
|
|
178
183
|
const { "x-api-key": _removed, ...filteredHeaders } = tokenData.headers;
|
|
179
|
-
if (_removed) {
|
|
180
|
-
debugLog(
|
|
181
|
-
"[gitlab-ai-provider] Filtered out x-api-key from headers (using authToken instead)"
|
|
182
|
-
);
|
|
183
|
-
}
|
|
184
184
|
this.anthropicClient = new Anthropic({
|
|
185
185
|
apiKey: null,
|
|
186
186
|
authToken: tokenData.token,
|
|
@@ -419,114 +419,155 @@ var GitLabAgenticLanguageModel = class {
|
|
|
419
419
|
const self = this;
|
|
420
420
|
const stream = new ReadableStream({
|
|
421
421
|
start: async (controller) => {
|
|
422
|
+
const contentBlocks = {};
|
|
423
|
+
const usage = {
|
|
424
|
+
inputTokens: 0,
|
|
425
|
+
outputTokens: 0,
|
|
426
|
+
totalTokens: 0
|
|
427
|
+
};
|
|
428
|
+
let finishReason = "unknown";
|
|
422
429
|
try {
|
|
423
|
-
const anthropicStream = client.messages.stream(requestBody
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
let currentToolName = null;
|
|
427
|
-
const usage = {
|
|
428
|
-
inputTokens: 0,
|
|
429
|
-
outputTokens: 0,
|
|
430
|
-
totalTokens: 0
|
|
431
|
-
};
|
|
432
|
-
let finishReason = "unknown";
|
|
430
|
+
const anthropicStream = client.messages.stream(requestBody, {
|
|
431
|
+
signal: options.abortSignal
|
|
432
|
+
});
|
|
433
433
|
controller.enqueue({
|
|
434
434
|
type: "stream-start",
|
|
435
435
|
warnings: []
|
|
436
436
|
});
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
type
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
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
|
-
|
|
437
|
+
await new Promise((resolve2, reject) => {
|
|
438
|
+
anthropicStream.on("streamEvent", (event) => {
|
|
439
|
+
try {
|
|
440
|
+
switch (event.type) {
|
|
441
|
+
case "message_start":
|
|
442
|
+
if (event.message.usage) {
|
|
443
|
+
usage.inputTokens = event.message.usage.input_tokens;
|
|
444
|
+
}
|
|
445
|
+
controller.enqueue({
|
|
446
|
+
type: "response-metadata",
|
|
447
|
+
id: event.message.id,
|
|
448
|
+
modelId: event.message.model
|
|
449
|
+
});
|
|
450
|
+
break;
|
|
451
|
+
case "content_block_start":
|
|
452
|
+
if (event.content_block.type === "text") {
|
|
453
|
+
const textId = `text-${event.index}`;
|
|
454
|
+
contentBlocks[event.index] = { type: "text", id: textId };
|
|
455
|
+
controller.enqueue({
|
|
456
|
+
type: "text-start",
|
|
457
|
+
id: textId
|
|
458
|
+
});
|
|
459
|
+
} else if (event.content_block.type === "tool_use") {
|
|
460
|
+
contentBlocks[event.index] = {
|
|
461
|
+
type: "tool-call",
|
|
462
|
+
toolCallId: event.content_block.id,
|
|
463
|
+
toolName: event.content_block.name,
|
|
464
|
+
input: ""
|
|
465
|
+
};
|
|
466
|
+
controller.enqueue({
|
|
467
|
+
type: "tool-input-start",
|
|
468
|
+
id: event.content_block.id,
|
|
469
|
+
toolName: event.content_block.name
|
|
470
|
+
});
|
|
471
|
+
}
|
|
472
|
+
break;
|
|
473
|
+
case "content_block_delta": {
|
|
474
|
+
const block = contentBlocks[event.index];
|
|
475
|
+
if (event.delta.type === "text_delta" && block?.type === "text") {
|
|
476
|
+
controller.enqueue({
|
|
477
|
+
type: "text-delta",
|
|
478
|
+
id: block.id,
|
|
479
|
+
delta: event.delta.text
|
|
480
|
+
});
|
|
481
|
+
} else if (event.delta.type === "input_json_delta" && block?.type === "tool-call") {
|
|
482
|
+
block.input += event.delta.partial_json;
|
|
483
|
+
controller.enqueue({
|
|
484
|
+
type: "tool-input-delta",
|
|
485
|
+
id: block.toolCallId,
|
|
486
|
+
delta: event.delta.partial_json
|
|
487
|
+
});
|
|
488
|
+
}
|
|
489
|
+
break;
|
|
490
|
+
}
|
|
491
|
+
case "content_block_stop": {
|
|
492
|
+
const block = contentBlocks[event.index];
|
|
493
|
+
if (block?.type === "text") {
|
|
494
|
+
controller.enqueue({
|
|
495
|
+
type: "text-end",
|
|
496
|
+
id: block.id
|
|
497
|
+
});
|
|
498
|
+
} else if (block?.type === "tool-call") {
|
|
499
|
+
controller.enqueue({
|
|
500
|
+
type: "tool-input-end",
|
|
501
|
+
id: block.toolCallId
|
|
502
|
+
});
|
|
503
|
+
controller.enqueue({
|
|
504
|
+
type: "tool-call",
|
|
505
|
+
toolCallId: block.toolCallId,
|
|
506
|
+
toolName: block.toolName,
|
|
507
|
+
input: block.input === "" ? "{}" : block.input
|
|
508
|
+
});
|
|
509
|
+
}
|
|
510
|
+
delete contentBlocks[event.index];
|
|
511
|
+
break;
|
|
512
|
+
}
|
|
513
|
+
case "message_delta":
|
|
514
|
+
if (event.usage) {
|
|
515
|
+
usage.outputTokens = event.usage.output_tokens;
|
|
516
|
+
usage.totalTokens = (usage.inputTokens || 0) + event.usage.output_tokens;
|
|
517
|
+
}
|
|
518
|
+
if (event.delta.stop_reason) {
|
|
519
|
+
finishReason = self.convertFinishReason(event.delta.stop_reason);
|
|
520
|
+
}
|
|
521
|
+
break;
|
|
522
|
+
case "message_stop": {
|
|
511
523
|
controller.enqueue({
|
|
512
|
-
type: "
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
input: JSON.stringify(block.input)
|
|
524
|
+
type: "finish",
|
|
525
|
+
finishReason,
|
|
526
|
+
usage
|
|
516
527
|
});
|
|
528
|
+
break;
|
|
517
529
|
}
|
|
518
530
|
}
|
|
519
|
-
|
|
520
|
-
type: "finish",
|
|
521
|
-
finishReason,
|
|
522
|
-
usage
|
|
523
|
-
});
|
|
524
|
-
break;
|
|
531
|
+
} catch {
|
|
525
532
|
}
|
|
533
|
+
});
|
|
534
|
+
anthropicStream.on("end", () => {
|
|
535
|
+
resolve2();
|
|
536
|
+
});
|
|
537
|
+
anthropicStream.on("error", (error) => {
|
|
538
|
+
reject(error);
|
|
539
|
+
});
|
|
540
|
+
});
|
|
541
|
+
for (const [, block] of Object.entries(contentBlocks)) {
|
|
542
|
+
if (block.type === "tool-call") {
|
|
543
|
+
controller.enqueue({
|
|
544
|
+
type: "tool-input-end",
|
|
545
|
+
id: block.toolCallId
|
|
546
|
+
});
|
|
547
|
+
controller.enqueue({
|
|
548
|
+
type: "tool-call",
|
|
549
|
+
toolCallId: block.toolCallId,
|
|
550
|
+
toolName: block.toolName,
|
|
551
|
+
input: block.input === "" ? "{}" : block.input
|
|
552
|
+
});
|
|
526
553
|
}
|
|
527
554
|
}
|
|
528
555
|
controller.close();
|
|
529
556
|
} catch (error) {
|
|
557
|
+
for (const [, block] of Object.entries(contentBlocks)) {
|
|
558
|
+
if (block.type === "tool-call") {
|
|
559
|
+
controller.enqueue({
|
|
560
|
+
type: "tool-input-end",
|
|
561
|
+
id: block.toolCallId
|
|
562
|
+
});
|
|
563
|
+
controller.enqueue({
|
|
564
|
+
type: "tool-call",
|
|
565
|
+
toolCallId: block.toolCallId,
|
|
566
|
+
toolName: block.toolName,
|
|
567
|
+
input: block.input === "" ? "{}" : block.input
|
|
568
|
+
});
|
|
569
|
+
}
|
|
570
|
+
}
|
|
530
571
|
if (!isRetry && self.isTokenError(error)) {
|
|
531
572
|
self.directAccessClient.invalidateToken();
|
|
532
573
|
controller.enqueue({
|
|
@@ -564,6 +605,740 @@ var GitLabAgenticLanguageModel = class {
|
|
|
564
605
|
}
|
|
565
606
|
};
|
|
566
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-mini": {
|
|
620
|
+
provider: "openai",
|
|
621
|
+
model: "gpt-5-mini-2025-08-07",
|
|
622
|
+
openaiApiType: "chat"
|
|
623
|
+
},
|
|
624
|
+
// OpenAI models - Responses API (Codex models)
|
|
625
|
+
"duo-chat-gpt-5-codex": { provider: "openai", model: "gpt-5-codex", openaiApiType: "responses" },
|
|
626
|
+
"duo-chat-gpt-5-2-codex": {
|
|
627
|
+
provider: "openai",
|
|
628
|
+
model: "gpt-5.2-codex",
|
|
629
|
+
openaiApiType: "responses"
|
|
630
|
+
}
|
|
631
|
+
};
|
|
632
|
+
function getModelMapping(modelId) {
|
|
633
|
+
return MODEL_MAPPINGS[modelId];
|
|
634
|
+
}
|
|
635
|
+
function getProviderForModelId(modelId) {
|
|
636
|
+
return MODEL_MAPPINGS[modelId]?.provider;
|
|
637
|
+
}
|
|
638
|
+
function getValidModelsForProvider(provider) {
|
|
639
|
+
return Object.values(MODEL_MAPPINGS).filter((m) => m.provider === provider).map((m) => m.model);
|
|
640
|
+
}
|
|
641
|
+
function getAnthropicModelForModelId(modelId) {
|
|
642
|
+
const mapping = MODEL_MAPPINGS[modelId];
|
|
643
|
+
return mapping?.provider === "anthropic" ? mapping.model : void 0;
|
|
644
|
+
}
|
|
645
|
+
function getOpenAIModelForModelId(modelId) {
|
|
646
|
+
const mapping = MODEL_MAPPINGS[modelId];
|
|
647
|
+
return mapping?.provider === "openai" ? mapping.model : void 0;
|
|
648
|
+
}
|
|
649
|
+
function getOpenAIApiType(modelId) {
|
|
650
|
+
const mapping = MODEL_MAPPINGS[modelId];
|
|
651
|
+
return mapping?.openaiApiType ?? "chat";
|
|
652
|
+
}
|
|
653
|
+
function isResponsesApiModel(modelId) {
|
|
654
|
+
return getOpenAIApiType(modelId) === "responses";
|
|
655
|
+
}
|
|
656
|
+
var MODEL_ID_TO_ANTHROPIC_MODEL = Object.fromEntries(
|
|
657
|
+
Object.entries(MODEL_MAPPINGS).filter(([, v]) => v.provider === "anthropic").map(([k, v]) => [k, v.model])
|
|
658
|
+
);
|
|
659
|
+
|
|
660
|
+
// src/gitlab-openai-language-model.ts
|
|
661
|
+
var GitLabOpenAILanguageModel = class {
|
|
662
|
+
specificationVersion = "v2";
|
|
663
|
+
modelId;
|
|
664
|
+
supportedUrls = {};
|
|
665
|
+
config;
|
|
666
|
+
directAccessClient;
|
|
667
|
+
useResponsesApi;
|
|
668
|
+
openaiClient = null;
|
|
669
|
+
constructor(modelId, config) {
|
|
670
|
+
this.modelId = modelId;
|
|
671
|
+
this.config = config;
|
|
672
|
+
this.useResponsesApi = config.useResponsesApi ?? isResponsesApiModel(modelId);
|
|
673
|
+
this.directAccessClient = new GitLabDirectAccessClient({
|
|
674
|
+
instanceUrl: config.instanceUrl,
|
|
675
|
+
getHeaders: config.getHeaders,
|
|
676
|
+
refreshApiKey: config.refreshApiKey,
|
|
677
|
+
fetch: config.fetch,
|
|
678
|
+
featureFlags: config.featureFlags,
|
|
679
|
+
aiGatewayUrl: config.aiGatewayUrl
|
|
680
|
+
});
|
|
681
|
+
}
|
|
682
|
+
get provider() {
|
|
683
|
+
return this.config.provider;
|
|
684
|
+
}
|
|
685
|
+
async getOpenAIClient(forceRefresh = false) {
|
|
686
|
+
const tokenData = await this.directAccessClient.getDirectAccessToken(forceRefresh);
|
|
687
|
+
const { "x-api-key": _removed, ...filteredHeaders } = tokenData.headers;
|
|
688
|
+
this.openaiClient = new OpenAI({
|
|
689
|
+
apiKey: tokenData.token,
|
|
690
|
+
baseURL: this.directAccessClient.getOpenAIProxyUrl(),
|
|
691
|
+
defaultHeaders: filteredHeaders
|
|
692
|
+
});
|
|
693
|
+
return this.openaiClient;
|
|
694
|
+
}
|
|
695
|
+
isTokenError(error) {
|
|
696
|
+
if (error instanceof OpenAI.APIError) {
|
|
697
|
+
if (error.status === 401) {
|
|
698
|
+
return true;
|
|
699
|
+
}
|
|
700
|
+
const message = error.message?.toLowerCase() || "";
|
|
701
|
+
if (message.includes("token") && (message.includes("expired") || message.includes("revoked") || message.includes("invalid"))) {
|
|
702
|
+
return true;
|
|
703
|
+
}
|
|
704
|
+
}
|
|
705
|
+
return false;
|
|
706
|
+
}
|
|
707
|
+
convertTools(tools) {
|
|
708
|
+
if (!tools || tools.length === 0) {
|
|
709
|
+
return void 0;
|
|
710
|
+
}
|
|
711
|
+
return tools.filter((tool) => tool.type === "function").map((tool) => {
|
|
712
|
+
const schema = tool.inputSchema;
|
|
713
|
+
return {
|
|
714
|
+
type: "function",
|
|
715
|
+
function: {
|
|
716
|
+
name: tool.name,
|
|
717
|
+
description: tool.description || "",
|
|
718
|
+
// Ensure the schema has type: 'object' as OpenAI requires it
|
|
719
|
+
parameters: {
|
|
720
|
+
type: "object",
|
|
721
|
+
...schema
|
|
722
|
+
}
|
|
723
|
+
}
|
|
724
|
+
};
|
|
725
|
+
});
|
|
726
|
+
}
|
|
727
|
+
convertToolChoice(toolChoice) {
|
|
728
|
+
if (!toolChoice) {
|
|
729
|
+
return void 0;
|
|
730
|
+
}
|
|
731
|
+
switch (toolChoice.type) {
|
|
732
|
+
case "auto":
|
|
733
|
+
return "auto";
|
|
734
|
+
case "none":
|
|
735
|
+
return "none";
|
|
736
|
+
case "required":
|
|
737
|
+
return "required";
|
|
738
|
+
case "tool":
|
|
739
|
+
return { type: "function", function: { name: toolChoice.toolName } };
|
|
740
|
+
default:
|
|
741
|
+
return void 0;
|
|
742
|
+
}
|
|
743
|
+
}
|
|
744
|
+
convertPrompt(prompt) {
|
|
745
|
+
const messages = [];
|
|
746
|
+
for (const message of prompt) {
|
|
747
|
+
if (message.role === "system") {
|
|
748
|
+
messages.push({ role: "system", content: message.content });
|
|
749
|
+
continue;
|
|
750
|
+
}
|
|
751
|
+
if (message.role === "user") {
|
|
752
|
+
const textParts = message.content.filter((part) => part.type === "text").map((part) => part.text);
|
|
753
|
+
if (textParts.length > 0) {
|
|
754
|
+
messages.push({ role: "user", content: textParts.join("\n") });
|
|
755
|
+
}
|
|
756
|
+
} else if (message.role === "assistant") {
|
|
757
|
+
const textParts = [];
|
|
758
|
+
const toolCalls = [];
|
|
759
|
+
for (const part of message.content) {
|
|
760
|
+
if (part.type === "text") {
|
|
761
|
+
textParts.push(part.text);
|
|
762
|
+
} else if (part.type === "tool-call") {
|
|
763
|
+
toolCalls.push({
|
|
764
|
+
id: part.toolCallId,
|
|
765
|
+
type: "function",
|
|
766
|
+
function: {
|
|
767
|
+
name: part.toolName,
|
|
768
|
+
arguments: typeof part.input === "string" ? part.input : JSON.stringify(part.input)
|
|
769
|
+
}
|
|
770
|
+
});
|
|
771
|
+
}
|
|
772
|
+
}
|
|
773
|
+
const assistantMessage = {
|
|
774
|
+
role: "assistant",
|
|
775
|
+
content: textParts.length > 0 ? textParts.join("\n") : null
|
|
776
|
+
};
|
|
777
|
+
if (toolCalls.length > 0) {
|
|
778
|
+
assistantMessage.tool_calls = toolCalls;
|
|
779
|
+
}
|
|
780
|
+
messages.push(assistantMessage);
|
|
781
|
+
} else if (message.role === "tool") {
|
|
782
|
+
for (const part of message.content) {
|
|
783
|
+
if (part.type === "tool-result") {
|
|
784
|
+
let resultContent;
|
|
785
|
+
if (part.output.type === "text") {
|
|
786
|
+
resultContent = part.output.value;
|
|
787
|
+
} else if (part.output.type === "json") {
|
|
788
|
+
resultContent = JSON.stringify(part.output.value);
|
|
789
|
+
} else if (part.output.type === "error-text") {
|
|
790
|
+
resultContent = part.output.value;
|
|
791
|
+
} else if (part.output.type === "error-json") {
|
|
792
|
+
resultContent = JSON.stringify(part.output.value);
|
|
793
|
+
} else {
|
|
794
|
+
resultContent = JSON.stringify(part.output);
|
|
795
|
+
}
|
|
796
|
+
messages.push({
|
|
797
|
+
role: "tool",
|
|
798
|
+
tool_call_id: part.toolCallId,
|
|
799
|
+
content: resultContent
|
|
800
|
+
});
|
|
801
|
+
}
|
|
802
|
+
}
|
|
803
|
+
}
|
|
804
|
+
}
|
|
805
|
+
return messages;
|
|
806
|
+
}
|
|
807
|
+
convertFinishReason(finishReason) {
|
|
808
|
+
switch (finishReason) {
|
|
809
|
+
case "stop":
|
|
810
|
+
return "stop";
|
|
811
|
+
case "length":
|
|
812
|
+
return "length";
|
|
813
|
+
case "tool_calls":
|
|
814
|
+
return "tool-calls";
|
|
815
|
+
case "content_filter":
|
|
816
|
+
return "content-filter";
|
|
817
|
+
default:
|
|
818
|
+
return "unknown";
|
|
819
|
+
}
|
|
820
|
+
}
|
|
821
|
+
/**
|
|
822
|
+
* Convert tools to Responses API format
|
|
823
|
+
*/
|
|
824
|
+
convertToolsForResponses(tools) {
|
|
825
|
+
if (!tools || tools.length === 0) {
|
|
826
|
+
return void 0;
|
|
827
|
+
}
|
|
828
|
+
return tools.filter((tool) => tool.type === "function").map((tool) => {
|
|
829
|
+
const schema = { ...tool.inputSchema };
|
|
830
|
+
delete schema["$schema"];
|
|
831
|
+
return {
|
|
832
|
+
type: "function",
|
|
833
|
+
name: tool.name,
|
|
834
|
+
description: tool.description || "",
|
|
835
|
+
parameters: schema,
|
|
836
|
+
strict: false
|
|
837
|
+
};
|
|
838
|
+
});
|
|
839
|
+
}
|
|
840
|
+
/**
|
|
841
|
+
* Convert prompt to Responses API input format
|
|
842
|
+
*/
|
|
843
|
+
convertPromptForResponses(prompt) {
|
|
844
|
+
const items = [];
|
|
845
|
+
for (const message of prompt) {
|
|
846
|
+
if (message.role === "system") {
|
|
847
|
+
continue;
|
|
848
|
+
}
|
|
849
|
+
if (message.role === "user") {
|
|
850
|
+
const textParts = message.content.filter((part) => part.type === "text").map((part) => part.text);
|
|
851
|
+
if (textParts.length > 0) {
|
|
852
|
+
items.push({
|
|
853
|
+
type: "message",
|
|
854
|
+
role: "user",
|
|
855
|
+
content: textParts.map((text) => ({ type: "input_text", text }))
|
|
856
|
+
});
|
|
857
|
+
}
|
|
858
|
+
} else if (message.role === "assistant") {
|
|
859
|
+
const textParts = [];
|
|
860
|
+
for (const part of message.content) {
|
|
861
|
+
if (part.type === "text") {
|
|
862
|
+
textParts.push(part.text);
|
|
863
|
+
} else if (part.type === "tool-call") {
|
|
864
|
+
items.push({
|
|
865
|
+
type: "function_call",
|
|
866
|
+
call_id: part.toolCallId,
|
|
867
|
+
name: part.toolName,
|
|
868
|
+
arguments: typeof part.input === "string" ? part.input : JSON.stringify(part.input)
|
|
869
|
+
});
|
|
870
|
+
}
|
|
871
|
+
}
|
|
872
|
+
if (textParts.length > 0) {
|
|
873
|
+
items.push({
|
|
874
|
+
type: "message",
|
|
875
|
+
role: "assistant",
|
|
876
|
+
content: [{ type: "output_text", text: textParts.join("\n"), annotations: [] }]
|
|
877
|
+
});
|
|
878
|
+
}
|
|
879
|
+
} else if (message.role === "tool") {
|
|
880
|
+
for (const part of message.content) {
|
|
881
|
+
if (part.type === "tool-result") {
|
|
882
|
+
let resultContent;
|
|
883
|
+
if (part.output.type === "text") {
|
|
884
|
+
resultContent = part.output.value;
|
|
885
|
+
} else if (part.output.type === "json") {
|
|
886
|
+
resultContent = JSON.stringify(part.output.value);
|
|
887
|
+
} else if (part.output.type === "error-text") {
|
|
888
|
+
resultContent = part.output.value;
|
|
889
|
+
} else if (part.output.type === "error-json") {
|
|
890
|
+
resultContent = JSON.stringify(part.output.value);
|
|
891
|
+
} else {
|
|
892
|
+
resultContent = JSON.stringify(part.output);
|
|
893
|
+
}
|
|
894
|
+
items.push({
|
|
895
|
+
type: "function_call_output",
|
|
896
|
+
call_id: part.toolCallId,
|
|
897
|
+
output: resultContent
|
|
898
|
+
});
|
|
899
|
+
}
|
|
900
|
+
}
|
|
901
|
+
}
|
|
902
|
+
}
|
|
903
|
+
return items;
|
|
904
|
+
}
|
|
905
|
+
/**
|
|
906
|
+
* Extract system instructions from prompt
|
|
907
|
+
*/
|
|
908
|
+
extractSystemInstructions(prompt) {
|
|
909
|
+
const systemMessages = prompt.filter((m) => m.role === "system").map((m) => m.content).join("\n");
|
|
910
|
+
return systemMessages || void 0;
|
|
911
|
+
}
|
|
912
|
+
/**
|
|
913
|
+
* Convert Responses API status to finish reason
|
|
914
|
+
* Note: Responses API returns 'completed' even when making tool calls,
|
|
915
|
+
* so we need to check the content for tool calls separately.
|
|
916
|
+
*/
|
|
917
|
+
convertResponsesStatus(status, hasToolCalls = false) {
|
|
918
|
+
if (hasToolCalls) {
|
|
919
|
+
return "tool-calls";
|
|
920
|
+
}
|
|
921
|
+
switch (status) {
|
|
922
|
+
case "completed":
|
|
923
|
+
return "stop";
|
|
924
|
+
case "incomplete":
|
|
925
|
+
return "length";
|
|
926
|
+
case "cancelled":
|
|
927
|
+
return "stop";
|
|
928
|
+
case "failed":
|
|
929
|
+
return "error";
|
|
930
|
+
default:
|
|
931
|
+
return "unknown";
|
|
932
|
+
}
|
|
933
|
+
}
|
|
934
|
+
async doGenerate(options) {
|
|
935
|
+
if (this.useResponsesApi) {
|
|
936
|
+
return this.doGenerateWithResponsesApi(options, false);
|
|
937
|
+
}
|
|
938
|
+
return this.doGenerateWithChatApi(options, false);
|
|
939
|
+
}
|
|
940
|
+
async doGenerateWithChatApi(options, isRetry) {
|
|
941
|
+
const client = await this.getOpenAIClient(isRetry);
|
|
942
|
+
const messages = this.convertPrompt(options.prompt);
|
|
943
|
+
const tools = this.convertTools(options.tools);
|
|
944
|
+
const toolChoice = options.toolChoice?.type !== "none" ? this.convertToolChoice(options.toolChoice) : void 0;
|
|
945
|
+
const openaiModel = this.config.openaiModel || "gpt-4o";
|
|
946
|
+
const maxTokens = options.maxOutputTokens || this.config.maxTokens || 8192;
|
|
947
|
+
try {
|
|
948
|
+
const response = await client.chat.completions.create({
|
|
949
|
+
model: openaiModel,
|
|
950
|
+
max_completion_tokens: maxTokens,
|
|
951
|
+
messages,
|
|
952
|
+
tools,
|
|
953
|
+
tool_choice: tools ? toolChoice : void 0,
|
|
954
|
+
temperature: options.temperature,
|
|
955
|
+
top_p: options.topP,
|
|
956
|
+
stop: options.stopSequences
|
|
957
|
+
});
|
|
958
|
+
const choice = response.choices[0];
|
|
959
|
+
const content = [];
|
|
960
|
+
if (choice?.message.content) {
|
|
961
|
+
content.push({ type: "text", text: choice.message.content });
|
|
962
|
+
}
|
|
963
|
+
if (choice?.message.tool_calls) {
|
|
964
|
+
for (const toolCall of choice.message.tool_calls) {
|
|
965
|
+
if (toolCall.type === "function") {
|
|
966
|
+
content.push({
|
|
967
|
+
type: "tool-call",
|
|
968
|
+
toolCallId: toolCall.id,
|
|
969
|
+
toolName: toolCall.function.name,
|
|
970
|
+
input: toolCall.function.arguments
|
|
971
|
+
});
|
|
972
|
+
}
|
|
973
|
+
}
|
|
974
|
+
}
|
|
975
|
+
const usage = {
|
|
976
|
+
inputTokens: response.usage?.prompt_tokens || 0,
|
|
977
|
+
outputTokens: response.usage?.completion_tokens || 0,
|
|
978
|
+
totalTokens: response.usage?.total_tokens || 0
|
|
979
|
+
};
|
|
980
|
+
return {
|
|
981
|
+
content,
|
|
982
|
+
finishReason: this.convertFinishReason(choice?.finish_reason),
|
|
983
|
+
usage,
|
|
984
|
+
warnings: []
|
|
985
|
+
};
|
|
986
|
+
} catch (error) {
|
|
987
|
+
if (!isRetry && this.isTokenError(error)) {
|
|
988
|
+
this.directAccessClient.invalidateToken();
|
|
989
|
+
return this.doGenerateWithChatApi(options, true);
|
|
990
|
+
}
|
|
991
|
+
if (error instanceof OpenAI.APIError) {
|
|
992
|
+
throw new GitLabError({
|
|
993
|
+
message: `OpenAI API error: ${error.message}`,
|
|
994
|
+
cause: error
|
|
995
|
+
});
|
|
996
|
+
}
|
|
997
|
+
throw error;
|
|
998
|
+
}
|
|
999
|
+
}
|
|
1000
|
+
async doGenerateWithResponsesApi(options, isRetry) {
|
|
1001
|
+
const client = await this.getOpenAIClient(isRetry);
|
|
1002
|
+
const input = this.convertPromptForResponses(options.prompt);
|
|
1003
|
+
const tools = this.convertToolsForResponses(options.tools);
|
|
1004
|
+
const instructions = this.extractSystemInstructions(options.prompt);
|
|
1005
|
+
const openaiModel = this.config.openaiModel || "gpt-5-codex";
|
|
1006
|
+
const maxTokens = options.maxOutputTokens || this.config.maxTokens || 8192;
|
|
1007
|
+
try {
|
|
1008
|
+
const response = await client.responses.create({
|
|
1009
|
+
model: openaiModel,
|
|
1010
|
+
input,
|
|
1011
|
+
instructions,
|
|
1012
|
+
tools,
|
|
1013
|
+
max_output_tokens: maxTokens,
|
|
1014
|
+
temperature: options.temperature,
|
|
1015
|
+
top_p: options.topP,
|
|
1016
|
+
store: false
|
|
1017
|
+
});
|
|
1018
|
+
const content = [];
|
|
1019
|
+
let hasToolCalls = false;
|
|
1020
|
+
for (const item of response.output || []) {
|
|
1021
|
+
if (item.type === "message" && item.role === "assistant") {
|
|
1022
|
+
for (const contentItem of item.content || []) {
|
|
1023
|
+
if (contentItem.type === "output_text") {
|
|
1024
|
+
content.push({ type: "text", text: contentItem.text });
|
|
1025
|
+
}
|
|
1026
|
+
}
|
|
1027
|
+
} else if (item.type === "function_call") {
|
|
1028
|
+
hasToolCalls = true;
|
|
1029
|
+
content.push({
|
|
1030
|
+
type: "tool-call",
|
|
1031
|
+
toolCallId: item.call_id,
|
|
1032
|
+
toolName: item.name,
|
|
1033
|
+
input: item.arguments
|
|
1034
|
+
});
|
|
1035
|
+
}
|
|
1036
|
+
}
|
|
1037
|
+
const usage = {
|
|
1038
|
+
inputTokens: response.usage?.input_tokens || 0,
|
|
1039
|
+
outputTokens: response.usage?.output_tokens || 0,
|
|
1040
|
+
totalTokens: response.usage?.total_tokens || 0
|
|
1041
|
+
};
|
|
1042
|
+
return {
|
|
1043
|
+
content,
|
|
1044
|
+
finishReason: this.convertResponsesStatus(response.status, hasToolCalls),
|
|
1045
|
+
usage,
|
|
1046
|
+
warnings: []
|
|
1047
|
+
};
|
|
1048
|
+
} catch (error) {
|
|
1049
|
+
if (!isRetry && this.isTokenError(error)) {
|
|
1050
|
+
this.directAccessClient.invalidateToken();
|
|
1051
|
+
return this.doGenerateWithResponsesApi(options, true);
|
|
1052
|
+
}
|
|
1053
|
+
if (error instanceof OpenAI.APIError) {
|
|
1054
|
+
throw new GitLabError({
|
|
1055
|
+
message: `OpenAI API error: ${error.message}`,
|
|
1056
|
+
cause: error
|
|
1057
|
+
});
|
|
1058
|
+
}
|
|
1059
|
+
throw error;
|
|
1060
|
+
}
|
|
1061
|
+
}
|
|
1062
|
+
async doStream(options) {
|
|
1063
|
+
if (this.useResponsesApi) {
|
|
1064
|
+
return this.doStreamWithResponsesApi(options, false);
|
|
1065
|
+
}
|
|
1066
|
+
return this.doStreamWithChatApi(options, false);
|
|
1067
|
+
}
|
|
1068
|
+
async doStreamWithChatApi(options, isRetry) {
|
|
1069
|
+
const client = await this.getOpenAIClient(isRetry);
|
|
1070
|
+
const messages = this.convertPrompt(options.prompt);
|
|
1071
|
+
const tools = this.convertTools(options.tools);
|
|
1072
|
+
const toolChoice = options.toolChoice?.type !== "none" ? this.convertToolChoice(options.toolChoice) : void 0;
|
|
1073
|
+
const openaiModel = this.config.openaiModel || "gpt-4o";
|
|
1074
|
+
const maxTokens = options.maxOutputTokens || this.config.maxTokens || 8192;
|
|
1075
|
+
const requestBody = {
|
|
1076
|
+
model: openaiModel,
|
|
1077
|
+
max_completion_tokens: maxTokens,
|
|
1078
|
+
messages,
|
|
1079
|
+
tools,
|
|
1080
|
+
tool_choice: tools ? toolChoice : void 0,
|
|
1081
|
+
temperature: options.temperature,
|
|
1082
|
+
top_p: options.topP,
|
|
1083
|
+
stop: options.stopSequences,
|
|
1084
|
+
stream: true,
|
|
1085
|
+
stream_options: { include_usage: true }
|
|
1086
|
+
};
|
|
1087
|
+
const self = this;
|
|
1088
|
+
const stream = new ReadableStream({
|
|
1089
|
+
start: async (controller) => {
|
|
1090
|
+
const toolCalls = {};
|
|
1091
|
+
const usage = {
|
|
1092
|
+
inputTokens: 0,
|
|
1093
|
+
outputTokens: 0,
|
|
1094
|
+
totalTokens: 0
|
|
1095
|
+
};
|
|
1096
|
+
let finishReason = "unknown";
|
|
1097
|
+
let textStarted = false;
|
|
1098
|
+
const textId = "text-0";
|
|
1099
|
+
try {
|
|
1100
|
+
const openaiStream = await client.chat.completions.create({
|
|
1101
|
+
...requestBody,
|
|
1102
|
+
stream: true
|
|
1103
|
+
});
|
|
1104
|
+
controller.enqueue({ type: "stream-start", warnings: [] });
|
|
1105
|
+
for await (const chunk of openaiStream) {
|
|
1106
|
+
const choice = chunk.choices?.[0];
|
|
1107
|
+
if (chunk.id && !textStarted) {
|
|
1108
|
+
controller.enqueue({
|
|
1109
|
+
type: "response-metadata",
|
|
1110
|
+
id: chunk.id,
|
|
1111
|
+
modelId: chunk.model
|
|
1112
|
+
});
|
|
1113
|
+
}
|
|
1114
|
+
if (choice?.delta?.content) {
|
|
1115
|
+
if (!textStarted) {
|
|
1116
|
+
controller.enqueue({ type: "text-start", id: textId });
|
|
1117
|
+
textStarted = true;
|
|
1118
|
+
}
|
|
1119
|
+
controller.enqueue({
|
|
1120
|
+
type: "text-delta",
|
|
1121
|
+
id: textId,
|
|
1122
|
+
delta: choice.delta.content
|
|
1123
|
+
});
|
|
1124
|
+
}
|
|
1125
|
+
if (choice?.delta?.tool_calls) {
|
|
1126
|
+
for (const tc of choice.delta.tool_calls) {
|
|
1127
|
+
const idx = tc.index;
|
|
1128
|
+
if (!toolCalls[idx]) {
|
|
1129
|
+
toolCalls[idx] = {
|
|
1130
|
+
id: tc.id || "",
|
|
1131
|
+
name: tc.function?.name || "",
|
|
1132
|
+
arguments: ""
|
|
1133
|
+
};
|
|
1134
|
+
controller.enqueue({
|
|
1135
|
+
type: "tool-input-start",
|
|
1136
|
+
id: toolCalls[idx].id,
|
|
1137
|
+
toolName: toolCalls[idx].name
|
|
1138
|
+
});
|
|
1139
|
+
}
|
|
1140
|
+
if (tc.function?.arguments) {
|
|
1141
|
+
toolCalls[idx].arguments += tc.function.arguments;
|
|
1142
|
+
controller.enqueue({
|
|
1143
|
+
type: "tool-input-delta",
|
|
1144
|
+
id: toolCalls[idx].id,
|
|
1145
|
+
delta: tc.function.arguments
|
|
1146
|
+
});
|
|
1147
|
+
}
|
|
1148
|
+
}
|
|
1149
|
+
}
|
|
1150
|
+
if (choice?.finish_reason) {
|
|
1151
|
+
finishReason = self.convertFinishReason(choice.finish_reason);
|
|
1152
|
+
}
|
|
1153
|
+
if (chunk.usage) {
|
|
1154
|
+
usage.inputTokens = chunk.usage.prompt_tokens || 0;
|
|
1155
|
+
usage.outputTokens = chunk.usage.completion_tokens || 0;
|
|
1156
|
+
usage.totalTokens = chunk.usage.total_tokens || 0;
|
|
1157
|
+
}
|
|
1158
|
+
}
|
|
1159
|
+
if (textStarted) {
|
|
1160
|
+
controller.enqueue({ type: "text-end", id: textId });
|
|
1161
|
+
}
|
|
1162
|
+
for (const [, tc] of Object.entries(toolCalls)) {
|
|
1163
|
+
controller.enqueue({ type: "tool-input-end", id: tc.id });
|
|
1164
|
+
controller.enqueue({
|
|
1165
|
+
type: "tool-call",
|
|
1166
|
+
toolCallId: tc.id,
|
|
1167
|
+
toolName: tc.name,
|
|
1168
|
+
input: tc.arguments || "{}"
|
|
1169
|
+
});
|
|
1170
|
+
}
|
|
1171
|
+
controller.enqueue({ type: "finish", finishReason, usage });
|
|
1172
|
+
controller.close();
|
|
1173
|
+
} catch (error) {
|
|
1174
|
+
if (!isRetry && self.isTokenError(error)) {
|
|
1175
|
+
self.directAccessClient.invalidateToken();
|
|
1176
|
+
controller.enqueue({
|
|
1177
|
+
type: "error",
|
|
1178
|
+
error: new GitLabError({ message: "TOKEN_REFRESH_NEEDED", cause: error })
|
|
1179
|
+
});
|
|
1180
|
+
controller.close();
|
|
1181
|
+
return;
|
|
1182
|
+
}
|
|
1183
|
+
if (error instanceof OpenAI.APIError) {
|
|
1184
|
+
controller.enqueue({
|
|
1185
|
+
type: "error",
|
|
1186
|
+
error: new GitLabError({
|
|
1187
|
+
message: `OpenAI API error: ${error.message}`,
|
|
1188
|
+
cause: error
|
|
1189
|
+
})
|
|
1190
|
+
});
|
|
1191
|
+
} else {
|
|
1192
|
+
controller.enqueue({ type: "error", error });
|
|
1193
|
+
}
|
|
1194
|
+
controller.close();
|
|
1195
|
+
}
|
|
1196
|
+
}
|
|
1197
|
+
});
|
|
1198
|
+
return { stream, request: { body: requestBody } };
|
|
1199
|
+
}
|
|
1200
|
+
async doStreamWithResponsesApi(options, isRetry) {
|
|
1201
|
+
const client = await this.getOpenAIClient(isRetry);
|
|
1202
|
+
const input = this.convertPromptForResponses(options.prompt);
|
|
1203
|
+
const tools = this.convertToolsForResponses(options.tools);
|
|
1204
|
+
const instructions = this.extractSystemInstructions(options.prompt);
|
|
1205
|
+
const openaiModel = this.config.openaiModel || "gpt-5-codex";
|
|
1206
|
+
const maxTokens = options.maxOutputTokens || this.config.maxTokens || 8192;
|
|
1207
|
+
const requestBody = {
|
|
1208
|
+
model: openaiModel,
|
|
1209
|
+
input,
|
|
1210
|
+
instructions,
|
|
1211
|
+
tools,
|
|
1212
|
+
max_output_tokens: maxTokens,
|
|
1213
|
+
temperature: options.temperature,
|
|
1214
|
+
top_p: options.topP,
|
|
1215
|
+
store: false,
|
|
1216
|
+
stream: true
|
|
1217
|
+
};
|
|
1218
|
+
const self = this;
|
|
1219
|
+
const stream = new ReadableStream({
|
|
1220
|
+
start: async (controller) => {
|
|
1221
|
+
const toolCalls = {};
|
|
1222
|
+
const usage = {
|
|
1223
|
+
inputTokens: 0,
|
|
1224
|
+
outputTokens: 0,
|
|
1225
|
+
totalTokens: 0
|
|
1226
|
+
};
|
|
1227
|
+
let finishReason = "unknown";
|
|
1228
|
+
let textStarted = false;
|
|
1229
|
+
const textId = "text-0";
|
|
1230
|
+
try {
|
|
1231
|
+
const openaiStream = await client.responses.create({
|
|
1232
|
+
...requestBody,
|
|
1233
|
+
stream: true
|
|
1234
|
+
});
|
|
1235
|
+
controller.enqueue({ type: "stream-start", warnings: [] });
|
|
1236
|
+
for await (const event of openaiStream) {
|
|
1237
|
+
if (event.type === "response.created") {
|
|
1238
|
+
controller.enqueue({
|
|
1239
|
+
type: "response-metadata",
|
|
1240
|
+
id: event.response.id,
|
|
1241
|
+
modelId: event.response.model
|
|
1242
|
+
});
|
|
1243
|
+
} else if (event.type === "response.output_item.added") {
|
|
1244
|
+
if (event.item.type === "function_call") {
|
|
1245
|
+
const outputIndex = event.output_index;
|
|
1246
|
+
const callId = event.item.call_id;
|
|
1247
|
+
toolCalls[outputIndex] = {
|
|
1248
|
+
callId,
|
|
1249
|
+
name: event.item.name,
|
|
1250
|
+
arguments: ""
|
|
1251
|
+
};
|
|
1252
|
+
controller.enqueue({
|
|
1253
|
+
type: "tool-input-start",
|
|
1254
|
+
id: callId,
|
|
1255
|
+
toolName: event.item.name
|
|
1256
|
+
});
|
|
1257
|
+
}
|
|
1258
|
+
} else if (event.type === "response.output_text.delta") {
|
|
1259
|
+
if (!textStarted) {
|
|
1260
|
+
controller.enqueue({ type: "text-start", id: textId });
|
|
1261
|
+
textStarted = true;
|
|
1262
|
+
}
|
|
1263
|
+
controller.enqueue({
|
|
1264
|
+
type: "text-delta",
|
|
1265
|
+
id: textId,
|
|
1266
|
+
delta: event.delta
|
|
1267
|
+
});
|
|
1268
|
+
} else if (event.type === "response.function_call_arguments.delta") {
|
|
1269
|
+
const outputIndex = event.output_index;
|
|
1270
|
+
const tc = toolCalls[outputIndex];
|
|
1271
|
+
if (tc) {
|
|
1272
|
+
tc.arguments += event.delta;
|
|
1273
|
+
controller.enqueue({
|
|
1274
|
+
type: "tool-input-delta",
|
|
1275
|
+
id: tc.callId,
|
|
1276
|
+
delta: event.delta
|
|
1277
|
+
});
|
|
1278
|
+
}
|
|
1279
|
+
} else if (event.type === "response.function_call_arguments.done") {
|
|
1280
|
+
const outputIndex = event.output_index;
|
|
1281
|
+
const tc = toolCalls[outputIndex];
|
|
1282
|
+
if (tc) {
|
|
1283
|
+
tc.arguments = event.arguments;
|
|
1284
|
+
}
|
|
1285
|
+
} else if (event.type === "response.completed") {
|
|
1286
|
+
const hasToolCalls2 = Object.keys(toolCalls).length > 0;
|
|
1287
|
+
finishReason = self.convertResponsesStatus(event.response.status, hasToolCalls2);
|
|
1288
|
+
if (event.response.usage) {
|
|
1289
|
+
usage.inputTokens = event.response.usage.input_tokens || 0;
|
|
1290
|
+
usage.outputTokens = event.response.usage.output_tokens || 0;
|
|
1291
|
+
usage.totalTokens = event.response.usage.total_tokens || 0;
|
|
1292
|
+
}
|
|
1293
|
+
}
|
|
1294
|
+
}
|
|
1295
|
+
if (textStarted) {
|
|
1296
|
+
controller.enqueue({ type: "text-end", id: textId });
|
|
1297
|
+
}
|
|
1298
|
+
const hasToolCalls = Object.keys(toolCalls).length > 0;
|
|
1299
|
+
if (hasToolCalls && finishReason === "stop") {
|
|
1300
|
+
finishReason = "tool-calls";
|
|
1301
|
+
}
|
|
1302
|
+
for (const tc of Object.values(toolCalls)) {
|
|
1303
|
+
controller.enqueue({ type: "tool-input-end", id: tc.callId });
|
|
1304
|
+
controller.enqueue({
|
|
1305
|
+
type: "tool-call",
|
|
1306
|
+
toolCallId: tc.callId,
|
|
1307
|
+
toolName: tc.name,
|
|
1308
|
+
input: tc.arguments || "{}"
|
|
1309
|
+
});
|
|
1310
|
+
}
|
|
1311
|
+
controller.enqueue({ type: "finish", finishReason, usage });
|
|
1312
|
+
controller.close();
|
|
1313
|
+
} catch (error) {
|
|
1314
|
+
if (!isRetry && self.isTokenError(error)) {
|
|
1315
|
+
self.directAccessClient.invalidateToken();
|
|
1316
|
+
controller.enqueue({
|
|
1317
|
+
type: "error",
|
|
1318
|
+
error: new GitLabError({ message: "TOKEN_REFRESH_NEEDED", cause: error })
|
|
1319
|
+
});
|
|
1320
|
+
controller.close();
|
|
1321
|
+
return;
|
|
1322
|
+
}
|
|
1323
|
+
if (error instanceof OpenAI.APIError) {
|
|
1324
|
+
controller.enqueue({
|
|
1325
|
+
type: "error",
|
|
1326
|
+
error: new GitLabError({
|
|
1327
|
+
message: `OpenAI API error: ${error.message}`,
|
|
1328
|
+
cause: error
|
|
1329
|
+
})
|
|
1330
|
+
});
|
|
1331
|
+
} else {
|
|
1332
|
+
controller.enqueue({ type: "error", error });
|
|
1333
|
+
}
|
|
1334
|
+
controller.close();
|
|
1335
|
+
}
|
|
1336
|
+
}
|
|
1337
|
+
});
|
|
1338
|
+
return { stream, request: { body: requestBody } };
|
|
1339
|
+
}
|
|
1340
|
+
};
|
|
1341
|
+
|
|
567
1342
|
// src/gitlab-oauth-types.ts
|
|
568
1343
|
var BUNDLED_CLIENT_ID = "36f2a70cddeb5a0889d4fd8295c241b7e9848e89cf9e599d0eed2d8e5350fbf5";
|
|
569
1344
|
var GITLAB_COM_URL = "https://gitlab.com";
|
|
@@ -726,16 +1501,6 @@ var GitLabOAuthManager = class {
|
|
|
726
1501
|
}
|
|
727
1502
|
};
|
|
728
1503
|
|
|
729
|
-
// src/model-mappings.ts
|
|
730
|
-
var MODEL_ID_TO_ANTHROPIC_MODEL = {
|
|
731
|
-
"duo-chat-opus-4-5": "claude-opus-4-5-20251101",
|
|
732
|
-
"duo-chat-sonnet-4-5": "claude-sonnet-4-5-20250929",
|
|
733
|
-
"duo-chat-haiku-4-5": "claude-haiku-4-5-20251001"
|
|
734
|
-
};
|
|
735
|
-
function getAnthropicModelForModelId(modelId) {
|
|
736
|
-
return MODEL_ID_TO_ANTHROPIC_MODEL[modelId];
|
|
737
|
-
}
|
|
738
|
-
|
|
739
1504
|
// src/gitlab-provider.ts
|
|
740
1505
|
import * as fs from "fs";
|
|
741
1506
|
import * as path from "path";
|
|
@@ -871,21 +1636,44 @@ function createGitLab(options = {}) {
|
|
|
871
1636
|
getApiKey().catch(() => {
|
|
872
1637
|
});
|
|
873
1638
|
const createAgenticChatModel = (modelId, agenticOptions) => {
|
|
1639
|
+
const mapping = getModelMapping(modelId);
|
|
1640
|
+
if (!mapping) {
|
|
1641
|
+
throw new GitLabError({
|
|
1642
|
+
message: `Unknown model ID: ${modelId}. Model must be registered in MODEL_MAPPINGS.`
|
|
1643
|
+
});
|
|
1644
|
+
}
|
|
1645
|
+
if (agenticOptions?.providerModel) {
|
|
1646
|
+
const validModels = getValidModelsForProvider(mapping.provider);
|
|
1647
|
+
if (!validModels.includes(agenticOptions.providerModel)) {
|
|
1648
|
+
throw new GitLabError({
|
|
1649
|
+
message: `Invalid providerModel '${agenticOptions.providerModel}' for provider '${mapping.provider}'. Valid models: ${validModels.join(", ")}`
|
|
1650
|
+
});
|
|
1651
|
+
}
|
|
1652
|
+
}
|
|
874
1653
|
const featureFlags = {
|
|
875
1654
|
DuoAgentPlatformNext: true,
|
|
876
1655
|
...options.featureFlags,
|
|
877
1656
|
...agenticOptions?.featureFlags
|
|
878
1657
|
};
|
|
879
|
-
|
|
1658
|
+
const baseConfig = {
|
|
880
1659
|
provider: `${providerName}.agentic`,
|
|
881
1660
|
instanceUrl,
|
|
882
1661
|
getHeaders,
|
|
883
1662
|
refreshApiKey,
|
|
884
1663
|
fetch: options.fetch,
|
|
885
|
-
anthropicModel: agenticOptions?.anthropicModel ?? getAnthropicModelForModelId(modelId),
|
|
886
1664
|
maxTokens: agenticOptions?.maxTokens,
|
|
887
1665
|
featureFlags,
|
|
888
1666
|
aiGatewayUrl: options.aiGatewayUrl
|
|
1667
|
+
};
|
|
1668
|
+
if (mapping.provider === "openai") {
|
|
1669
|
+
return new GitLabOpenAILanguageModel(modelId, {
|
|
1670
|
+
...baseConfig,
|
|
1671
|
+
openaiModel: agenticOptions?.providerModel ?? mapping.model
|
|
1672
|
+
});
|
|
1673
|
+
}
|
|
1674
|
+
return new GitLabAnthropicLanguageModel(modelId, {
|
|
1675
|
+
...baseConfig,
|
|
1676
|
+
anthropicModel: agenticOptions?.providerModel ?? mapping.model
|
|
889
1677
|
});
|
|
890
1678
|
};
|
|
891
1679
|
const createDefaultModel = (modelId) => {
|
|
@@ -996,7 +1784,7 @@ var GitLabProjectCache = class {
|
|
|
996
1784
|
};
|
|
997
1785
|
|
|
998
1786
|
// src/gitlab-project-detector.ts
|
|
999
|
-
var
|
|
1787
|
+
var debugLog = (..._args) => {
|
|
1000
1788
|
};
|
|
1001
1789
|
var GitLabProjectDetector = class {
|
|
1002
1790
|
config;
|
|
@@ -1025,35 +1813,35 @@ var GitLabProjectDetector = class {
|
|
|
1025
1813
|
return cached;
|
|
1026
1814
|
}
|
|
1027
1815
|
try {
|
|
1028
|
-
|
|
1816
|
+
debugLog(`[GitLabProjectDetector] Getting git remote URL from: ${workingDirectory}`);
|
|
1029
1817
|
const remoteUrl = await this.getGitRemoteUrl(workingDirectory, remoteName);
|
|
1030
1818
|
if (!remoteUrl) {
|
|
1031
|
-
|
|
1819
|
+
debugLog(`[GitLabProjectDetector] No git remote URL found`);
|
|
1032
1820
|
return null;
|
|
1033
1821
|
}
|
|
1034
|
-
|
|
1035
|
-
|
|
1822
|
+
debugLog(`[GitLabProjectDetector] Git remote URL: ${remoteUrl}`);
|
|
1823
|
+
debugLog(
|
|
1036
1824
|
`[GitLabProjectDetector] Parsing project path from URL (instance: ${this.config.instanceUrl})`
|
|
1037
1825
|
);
|
|
1038
1826
|
const projectPath = this.parseGitRemoteUrl(remoteUrl, this.config.instanceUrl);
|
|
1039
1827
|
if (!projectPath) {
|
|
1040
|
-
|
|
1828
|
+
debugLog(
|
|
1041
1829
|
`[GitLabProjectDetector] Could not parse project path from URL (remote doesn't match instance)`
|
|
1042
1830
|
);
|
|
1043
1831
|
return null;
|
|
1044
1832
|
}
|
|
1045
|
-
|
|
1046
|
-
|
|
1833
|
+
debugLog(`[GitLabProjectDetector] Parsed project path: ${projectPath}`);
|
|
1834
|
+
debugLog(`[GitLabProjectDetector] Fetching project from GitLab API: ${projectPath}`);
|
|
1047
1835
|
const project = await this.getProjectByPath(projectPath);
|
|
1048
|
-
|
|
1836
|
+
debugLog(`[GitLabProjectDetector] \u2713 Project fetched successfully:`, project);
|
|
1049
1837
|
this.cache.set(cacheKey, project);
|
|
1050
1838
|
return project;
|
|
1051
1839
|
} catch (error) {
|
|
1052
1840
|
if (error instanceof GitLabError) {
|
|
1053
|
-
|
|
1841
|
+
debugLog(`[GitLabProjectDetector] GitLab API error:`, error.message || error);
|
|
1054
1842
|
return null;
|
|
1055
1843
|
}
|
|
1056
|
-
|
|
1844
|
+
debugLog(`[GitLabProjectDetector] Unexpected error:`, error);
|
|
1057
1845
|
console.warn(`Failed to auto-detect GitLab project: ${error}`);
|
|
1058
1846
|
return null;
|
|
1059
1847
|
}
|
|
@@ -1184,17 +1972,25 @@ export {
|
|
|
1184
1972
|
BUNDLED_CLIENT_ID,
|
|
1185
1973
|
DEFAULT_AI_GATEWAY_URL,
|
|
1186
1974
|
GITLAB_COM_URL,
|
|
1187
|
-
|
|
1975
|
+
GitLabAnthropicLanguageModel,
|
|
1188
1976
|
GitLabDirectAccessClient,
|
|
1189
1977
|
GitLabError,
|
|
1190
1978
|
GitLabOAuthManager,
|
|
1979
|
+
GitLabOpenAILanguageModel,
|
|
1191
1980
|
GitLabProjectCache,
|
|
1192
1981
|
GitLabProjectDetector,
|
|
1193
1982
|
MODEL_ID_TO_ANTHROPIC_MODEL,
|
|
1983
|
+
MODEL_MAPPINGS,
|
|
1194
1984
|
OAUTH_SCOPES,
|
|
1195
1985
|
TOKEN_EXPIRY_SKEW_MS,
|
|
1196
1986
|
createGitLab,
|
|
1197
1987
|
getAnthropicModelForModelId,
|
|
1198
|
-
|
|
1988
|
+
getModelMapping,
|
|
1989
|
+
getOpenAIApiType,
|
|
1990
|
+
getOpenAIModelForModelId,
|
|
1991
|
+
getProviderForModelId,
|
|
1992
|
+
getValidModelsForProvider,
|
|
1993
|
+
gitlab,
|
|
1994
|
+
isResponsesApiModel
|
|
1199
1995
|
};
|
|
1200
1996
|
//# sourceMappingURL=index.mjs.map
|