{"version":3,"sources":["../src/aiplatform_llm.ts"],"sourcesContent":["// SPDX-FileCopyrightText: 2026 LiveKit, Inc.\n//\n// SPDX-License-Identifier: Apache-2.0\nimport type { APIConnectOptions } from '@livekit/agents';\nimport { DEFAULT_API_CONNECT_OPTIONS, inference, llm } from '@livekit/agents';\nimport { GoogleAuth } from 'google-auth-library';\nimport OpenAI from 'openai';\n\n/** @public */\nexport type ApiVersion = 'v1' | 'v1beta1';\n\n/** @public */\nexport type AccessTokenProvider = () => string | Promise<string>;\n\n/** @public */\nexport type GoogleCredentials = {\n  getAccessToken: () => Promise<string | null | undefined | { token?: string | null }>;\n};\n\n/** @public */\nexport interface AIPlatformLLMOptions {\n  /** Base DNS for the dedicated Model Garden endpoint, without path components. */\n  endpointURL: string;\n  /** Google Cloud project ID or number that owns the endpoint. */\n  project: string;\n  /** Numeric or UUID endpoint ID. */\n  endpointId: string;\n  location?: string;\n  model?: string;\n  accessToken?: string;\n  credentials?: GoogleCredentials;\n  tokenProvider?: AccessTokenProvider;\n  apiVersion?: ApiVersion;\n  temperature?: number;\n  topP?: number;\n  maxCompletionTokens?: number;\n  parallelToolCalls?: boolean;\n  toolChoice?: llm.ToolChoice;\n  extraBody?: Record<string, unknown>;\n  extraHeaders?: Record<string, string>;\n  extraQuery?: Record<string, string>;\n  strictToolSchema?: boolean;\n  client?: OpenAI;\n  timeoutMs?: number;\n}\n\ntype ResolvedAIPlatformLLMOptions = Required<\n  Pick<AIPlatformLLMOptions, 'apiVersion' | 'location' | 'model' | 'strictToolSchema'>\n> &\n  Omit<AIPlatformLLMOptions, 'apiVersion' | 'location' | 'model' | 'strictToolSchema' | 'client'>;\n\nfunction normalizeEndpointURL(endpointURL: string): string {\n  let end = endpointURL.length;\n  while (end > 0 && endpointURL[end - 1] === '/') {\n    end -= 1;\n  }\n  return endpointURL.slice(0, end);\n}\n\nfunction resolveTokenProvider(opts: {\n  accessToken?: string;\n  credentials?: GoogleCredentials;\n  tokenProvider?: AccessTokenProvider;\n}): AccessTokenProvider {\n  if (opts.tokenProvider) {\n    return opts.tokenProvider;\n  }\n\n  if (opts.credentials) {\n    return async () => {\n      const accessToken = await opts.credentials!.getAccessToken();\n      const token = typeof accessToken === 'string' ? accessToken : accessToken?.token;\n      if (!token) {\n        throw new Error('Google credentials did not return an access token');\n      }\n      return token;\n    };\n  }\n\n  if (opts.accessToken) {\n    return () => opts.accessToken!;\n  }\n\n  const auth = new GoogleAuth({\n    scopes: ['https://www.googleapis.com/auth/cloud-platform'],\n  });\n  let credentials: Promise<GoogleCredentials> | undefined;\n\n  return async () => {\n    credentials = credentials ?? (auth.getClient() as unknown as Promise<GoogleCredentials>);\n    const accessToken = await (await credentials).getAccessToken();\n    const token = typeof accessToken === 'string' ? accessToken : accessToken?.token;\n    if (!token) {\n      throw new Error('Google application default credentials did not return an access token');\n    }\n    return token;\n  };\n}\n\nfunction createGoogleAuthFetch(tokenProvider: AccessTokenProvider): typeof globalThis.fetch {\n  return async (input, init) => {\n    const headers = new Headers(init?.headers);\n    headers.set('Authorization', `Bearer ${await tokenProvider()}`);\n    return globalThis.fetch(input, { ...init, headers });\n  };\n}\n\n/**\n * LLM for self-deployed Vertex AI Model Garden endpoints with OpenAI-compatible chat completions.\n *\n * @public\n */\nexport class AIPlatformLLM extends llm.LLM {\n  #opts: ResolvedAIPlatformLLMOptions;\n  #client: OpenAI;\n\n  constructor(opts: AIPlatformLLMOptions) {\n    super();\n\n    const apiVersion = opts.apiVersion ?? 'v1beta1';\n    const location = opts.location ?? 'us-central1';\n    const model = opts.model ?? 'gemma';\n    const strictToolSchema = opts.strictToolSchema ?? true;\n\n    this.#opts = {\n      ...opts,\n      apiVersion,\n      location,\n      model,\n      strictToolSchema,\n    };\n\n    if (opts.client) {\n      this.#client = opts.client;\n      return;\n    }\n\n    const baseURL = `${normalizeEndpointURL(opts.endpointURL)}/${apiVersion}/projects/${opts.project}/locations/${location}/endpoints/${opts.endpointId}`;\n    this.#client = new OpenAI({\n      apiKey: 'ignored-auth-comes-from-google-auth',\n      baseURL,\n      fetch: createGoogleAuthFetch(\n        resolveTokenProvider({\n          accessToken: opts.accessToken,\n          credentials: opts.credentials,\n          tokenProvider: opts.tokenProvider,\n        }),\n      ),\n      maxRetries: 0,\n      timeout: opts.timeoutMs,\n    });\n  }\n\n  label(): string {\n    return 'google.AIPlatformLLM';\n  }\n\n  get model(): string {\n    return this.#opts.model;\n  }\n\n  get provider(): string {\n    return 'Vertex AI Model Garden';\n  }\n\n  chat({\n    chatCtx,\n    toolCtx,\n    connOptions = DEFAULT_API_CONNECT_OPTIONS,\n    parallelToolCalls,\n    toolChoice,\n    extraKwargs,\n  }: {\n    chatCtx: llm.ChatContext;\n    toolCtx?: llm.ToolContext;\n    connOptions?: APIConnectOptions;\n    parallelToolCalls?: boolean;\n    toolChoice?: llm.ToolChoice;\n    extraKwargs?: Record<string, unknown>;\n  }): inference.LLMStream {\n    const extras: Record<string, unknown> = { ...extraKwargs };\n\n    if (this.#opts.temperature !== undefined) {\n      extras.temperature = this.#opts.temperature;\n    }\n    if (this.#opts.topP !== undefined) {\n      extras.top_p = this.#opts.topP;\n    }\n    if (this.#opts.maxCompletionTokens !== undefined) {\n      extras.max_completion_tokens = this.#opts.maxCompletionTokens;\n    }\n    if (this.#opts.extraBody !== undefined) {\n      extras.extra_body = this.#opts.extraBody;\n    }\n    if (this.#opts.extraHeaders !== undefined) {\n      extras.extra_headers = this.#opts.extraHeaders;\n    }\n    if (this.#opts.extraQuery !== undefined) {\n      extras.extra_query = this.#opts.extraQuery;\n    }\n\n    parallelToolCalls =\n      parallelToolCalls !== undefined ? parallelToolCalls : this.#opts.parallelToolCalls;\n    if (toolCtx && Object.keys(toolCtx).length > 0 && parallelToolCalls !== undefined) {\n      extras.parallel_tool_calls = parallelToolCalls;\n    }\n\n    toolChoice = toolChoice !== undefined ? toolChoice : this.#opts.toolChoice;\n    if (toolChoice !== undefined) {\n      extras.tool_choice = toolChoice;\n    }\n\n    return new inference.LLMStream(this as unknown as inference.LLM, {\n      model: this.#opts.model,\n      providerFmt: 'openai',\n      // eslint-disable-next-line @typescript-eslint/no-explicit-any\n      client: this.#client as any,\n      chatCtx,\n      toolCtx,\n      connOptions,\n      modelOptions: extras,\n      strictToolSchema: this.#opts.strictToolSchema,\n      gatewayOptions: undefined,\n    });\n  }\n}\n"],"mappings":"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","names":["OpenAI"]}