feat: enable GitLab Agent Platform with workflow model discovery (#18014)
This commit is contained in:
committed by
GitHub
parent
51618e9cef
commit
05d3e65f76
@@ -40,7 +40,12 @@ import { createGateway } from "@ai-sdk/gateway"
|
||||
import { createTogetherAI } from "@ai-sdk/togetherai"
|
||||
import { createPerplexity } from "@ai-sdk/perplexity"
|
||||
import { createVercel } from "@ai-sdk/vercel"
|
||||
import { createGitLab, VERSION as GITLAB_PROVIDER_VERSION } from "@gitlab/gitlab-ai-provider"
|
||||
import {
|
||||
createGitLab,
|
||||
VERSION as GITLAB_PROVIDER_VERSION,
|
||||
isWorkflowModel,
|
||||
discoverWorkflowModels,
|
||||
} from "gitlab-ai-provider"
|
||||
import { fromNodeProviderChain } from "@aws-sdk/credential-providers"
|
||||
import { GoogleAuth } from "google-auth-library"
|
||||
import { ProviderTransform } from "./transform"
|
||||
@@ -124,18 +129,20 @@ export namespace Provider {
|
||||
"@ai-sdk/togetherai": createTogetherAI,
|
||||
"@ai-sdk/perplexity": createPerplexity,
|
||||
"@ai-sdk/vercel": createVercel,
|
||||
"@gitlab/gitlab-ai-provider": createGitLab,
|
||||
"gitlab-ai-provider": createGitLab,
|
||||
// @ts-ignore (TODO: kill this code so we dont have to maintain it)
|
||||
"@ai-sdk/github-copilot": createGitHubCopilotOpenAICompatible,
|
||||
}
|
||||
|
||||
type CustomModelLoader = (sdk: any, modelID: string, options?: Record<string, any>) => Promise<any>
|
||||
type CustomVarsLoader = (options: Record<string, any>) => Record<string, string>
|
||||
type CustomDiscoverModels = () => Promise<Record<string, Model>>
|
||||
type CustomLoader = (provider: Info) => Promise<{
|
||||
autoload: boolean
|
||||
getModel?: CustomModelLoader
|
||||
vars?: CustomVarsLoader
|
||||
options?: Record<string, any>
|
||||
discoverModels?: CustomDiscoverModels
|
||||
}>
|
||||
|
||||
function useLanguageModel(sdk: any) {
|
||||
@@ -533,28 +540,105 @@ export namespace Provider {
|
||||
...(providerConfig?.options?.aiGatewayHeaders || {}),
|
||||
}
|
||||
|
||||
const featureFlags = {
|
||||
duo_agent_platform_agentic_chat: true,
|
||||
duo_agent_platform: true,
|
||||
...(providerConfig?.options?.featureFlags || {}),
|
||||
}
|
||||
|
||||
return {
|
||||
autoload: !!apiKey,
|
||||
options: {
|
||||
instanceUrl,
|
||||
apiKey,
|
||||
aiGatewayHeaders,
|
||||
featureFlags: {
|
||||
duo_agent_platform_agentic_chat: true,
|
||||
duo_agent_platform: true,
|
||||
...(providerConfig?.options?.featureFlags || {}),
|
||||
},
|
||||
featureFlags,
|
||||
},
|
||||
async getModel(sdk: ReturnType<typeof createGitLab>, modelID: string) {
|
||||
async getModel(sdk: ReturnType<typeof createGitLab>, modelID: string, options?: Record<string, any>) {
|
||||
if (modelID.startsWith("duo-workflow-")) {
|
||||
const workflowRef = options?.workflowRef as string | undefined
|
||||
// Use the static mapping if it exists, otherwise use duo-workflow with selectedModelRef
|
||||
const sdkModelID = isWorkflowModel(modelID) ? modelID : "duo-workflow"
|
||||
const model = sdk.workflowChat(sdkModelID, {
|
||||
featureFlags,
|
||||
})
|
||||
if (workflowRef) {
|
||||
model.selectedModelRef = workflowRef
|
||||
}
|
||||
return model
|
||||
}
|
||||
return sdk.agenticChat(modelID, {
|
||||
aiGatewayHeaders,
|
||||
featureFlags: {
|
||||
duo_agent_platform_agentic_chat: true,
|
||||
duo_agent_platform: true,
|
||||
...(providerConfig?.options?.featureFlags || {}),
|
||||
},
|
||||
featureFlags,
|
||||
})
|
||||
},
|
||||
async discoverModels(): Promise<Record<string, Model>> {
|
||||
if (!apiKey) {
|
||||
log.info("gitlab model discovery skipped: no apiKey")
|
||||
return {}
|
||||
}
|
||||
|
||||
try {
|
||||
const token = apiKey
|
||||
const getHeaders = (): Record<string, string> =>
|
||||
auth?.type === "api" ? { "PRIVATE-TOKEN": token } : { Authorization: `Bearer ${token}` }
|
||||
|
||||
log.info("gitlab model discovery starting", { instanceUrl })
|
||||
const result = await discoverWorkflowModels(
|
||||
{ instanceUrl, getHeaders },
|
||||
{ workingDirectory: Instance.directory },
|
||||
)
|
||||
|
||||
if (!result.models.length) {
|
||||
log.info("gitlab model discovery skipped: no models found", {
|
||||
project: result.project ? { id: result.project.id, path: result.project.pathWithNamespace } : null,
|
||||
})
|
||||
return {}
|
||||
}
|
||||
|
||||
const models: Record<string, Model> = {}
|
||||
for (const m of result.models) {
|
||||
if (!input.models[m.id]) {
|
||||
models[m.id] = {
|
||||
id: ModelID.make(m.id),
|
||||
providerID: ProviderID.make("gitlab"),
|
||||
name: `Agent Platform (${m.name})`,
|
||||
family: "",
|
||||
api: {
|
||||
id: m.id,
|
||||
url: instanceUrl,
|
||||
npm: "gitlab-ai-provider",
|
||||
},
|
||||
status: "active",
|
||||
headers: {},
|
||||
options: { workflowRef: m.ref },
|
||||
cost: { input: 0, output: 0, cache: { read: 0, write: 0 } },
|
||||
limit: { context: m.context, output: m.output },
|
||||
capabilities: {
|
||||
temperature: false,
|
||||
reasoning: true,
|
||||
attachment: true,
|
||||
toolcall: true,
|
||||
input: { text: true, audio: false, image: true, video: false, pdf: true },
|
||||
output: { text: true, audio: false, image: false, video: false, pdf: false },
|
||||
interleaved: false,
|
||||
},
|
||||
release_date: "",
|
||||
variants: {},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
log.info("gitlab model discovery complete", {
|
||||
count: Object.keys(models).length,
|
||||
models: Object.keys(models),
|
||||
})
|
||||
return models
|
||||
} catch (e) {
|
||||
log.warn("gitlab model discovery failed", { error: e })
|
||||
return {}
|
||||
}
|
||||
},
|
||||
}
|
||||
},
|
||||
"cloudflare-workers-ai": async (input) => {
|
||||
@@ -853,6 +937,9 @@ export namespace Provider {
|
||||
const varsLoaders: {
|
||||
[providerID: string]: CustomVarsLoader
|
||||
} = {}
|
||||
const discoveryLoaders: {
|
||||
[providerID: string]: CustomDiscoverModels
|
||||
} = {}
|
||||
const sdk = new Map<string, SDK>()
|
||||
|
||||
log.info("init")
|
||||
@@ -1009,6 +1096,7 @@ export namespace Provider {
|
||||
if (result && (result.autoload || providers[providerID])) {
|
||||
if (result.getModel) modelLoaders[providerID] = result.getModel
|
||||
if (result.vars) varsLoaders[providerID] = result.vars
|
||||
if (result.discoverModels) discoveryLoaders[providerID] = result.discoverModels
|
||||
const opts = result.options ?? {}
|
||||
const patch: Partial<Info> = providers[providerID] ? { options: opts } : { source: "custom", options: opts }
|
||||
mergeProvider(providerID, patch)
|
||||
@@ -1070,6 +1158,18 @@ export namespace Provider {
|
||||
log.info("found", { providerID })
|
||||
}
|
||||
|
||||
const gitlab = ProviderID.make("gitlab")
|
||||
if (discoveryLoaders[gitlab] && providers[gitlab]) {
|
||||
await (async () => {
|
||||
const discovered = await discoveryLoaders[gitlab]()
|
||||
for (const [modelID, model] of Object.entries(discovered)) {
|
||||
if (!providers[gitlab].models[modelID]) {
|
||||
providers[gitlab].models[modelID] = model
|
||||
}
|
||||
}
|
||||
})().catch((e) => log.warn("state discovery error", { id: "gitlab", error: e }))
|
||||
}
|
||||
|
||||
return {
|
||||
models: languages,
|
||||
providers,
|
||||
@@ -1250,7 +1350,7 @@ export namespace Provider {
|
||||
|
||||
try {
|
||||
const language = s.modelLoaders[model.providerID]
|
||||
? await s.modelLoaders[model.providerID](sdk, model.api.id, provider.options)
|
||||
? await s.modelLoaders[model.providerID](sdk, model.api.id, { ...provider.options, ...model.options })
|
||||
: sdk.languageModel(model.api.id)
|
||||
s.models.set(key, language)
|
||||
return language
|
||||
|
||||
Reference in New Issue
Block a user