- 新端点 /ai/v1/audio/speech(xai.grok-tts)、/rerank(cohere.rerank-v4)、/moderations(OCI Guardrails) - Responses 放行 code_interpreter 与远程 mcp 工具,web_search/x_search 解除仅非流式限制 - 模型能力映射扩展:TEXT_RERANK→RERANK、TEXT_TO_AUDIO→TTS - AI 网关文档独立 docs/ai-gateway.md,字段兼容矩阵只列支持项;README 精简引用 - swagger 修缺:135 处响应注解具体化,RawMessage/联合类型统一渲染 AnyJSON,overrides 迁至 docs/.swaggo - CHANGELOG 0.4.0,版本段不再记日期;DASH_VERSION v0.4.0
307 lines
18 KiB
Go
307 lines
18 KiB
Go
// Package oci 封装 Oracle Cloud Infrastructure 官方 Go SDK,
|
||
// 对上层提供统一的客户端抽象;真实云请求只允许出现在本包。
|
||
package oci
|
||
|
||
import (
|
||
"context"
|
||
"errors"
|
||
"fmt"
|
||
"io"
|
||
"strings"
|
||
"sync"
|
||
"time"
|
||
|
||
"github.com/oracle/oci-go-sdk/v65/common"
|
||
"github.com/oracle/oci-go-sdk/v65/identity"
|
||
|
||
"oci-portal/internal/aiwire"
|
||
)
|
||
|
||
// cloudcmServiceName 是 OCI 云消费(Cloud Consumption Model)订阅的服务名,
|
||
// 账户类别信息挂在该订阅之下。
|
||
const cloudcmServiceName = "CLOUDCM"
|
||
|
||
// ErrNoCloudcmSubscription 表示租户下没有 serviceName=CLOUDCM 的订阅。
|
||
var ErrNoCloudcmSubscription = errors.New("no CLOUDCM subscription found")
|
||
|
||
// TenancyInfo 是测活成功后取得的租户基本信息。
|
||
type TenancyInfo struct {
|
||
Name string
|
||
HomeRegionKey string
|
||
}
|
||
|
||
// Client 是真实 OCI 调用的统一入口,service 层只依赖此接口。
|
||
type Client interface {
|
||
// ValidateKey 用 Identity GetTenancy 验证凭据有效性(测活)。
|
||
ValidateKey(ctx context.Context, cred Credentials) (TenancyInfo, error)
|
||
// FetchAccountProfile 查询 CLOUDCM 订阅并判定账户类别。
|
||
FetchAccountProfile(ctx context.Context, cred Credentials) (AccountProfile, error)
|
||
// ListCompartments 递归列出租户下全部 ACTIVE compartment(不含租户根)。
|
||
ListCompartments(ctx context.Context, cred Credentials) ([]Compartment, error)
|
||
// ListRegionSubscriptions 列出租户已订阅的区域。
|
||
ListRegionSubscriptions(ctx context.Context, cred Credentials) ([]RegionSubscription, error)
|
||
// SubscribeRegion 为租户订阅新区域;请求发往 homeRegion,操作不可撤销。
|
||
SubscribeRegion(ctx context.Context, cred Credentials, homeRegion, regionKey string) error
|
||
// ListLimits 查询服务配额,name 为大小写不敏感的模糊过滤,按需附带用量。
|
||
ListLimits(ctx context.Context, cred Credentials, q LimitsQuery) ([]LimitValue, error)
|
||
// ListLimitServices 列出配额体系支持的全部服务。
|
||
ListLimitServices(ctx context.Context, cred Credentials, region string) ([]LimitService, error)
|
||
// ListSubscriptions 列出租户全部订阅摘要。
|
||
ListSubscriptions(ctx context.Context, cred Credentials) ([]SubscriptionInfo, error)
|
||
// GetSubscription 查询单个订阅的完整信息。
|
||
GetSubscription(ctx context.Context, cred Credentials, subscriptionID string) (SubscriptionDetail, error)
|
||
// ListShapes 列出租户在目标区域可用的实例规格(带缓存)。
|
||
ListShapes(ctx context.Context, cred Credentials, region, availabilityDomain string) ([]ComputeShape, error)
|
||
// ListImages 列出可用的实例镜像。
|
||
ListImages(ctx context.Context, cred Credentials, q ImagesQuery) ([]Image, error)
|
||
// GetImage 查询单个镜像。
|
||
GetImage(ctx context.Context, cred Credentials, region, imageID string) (Image, error)
|
||
// VCN 增删改查与一键 IPv6。
|
||
ListVCNs(ctx context.Context, cred Credentials, region string) ([]VCN, error)
|
||
CreateVCN(ctx context.Context, cred Credentials, in CreateVCNInput) (VCN, error)
|
||
GetVCN(ctx context.Context, cred Credentials, region, vcnID string) (VCN, error)
|
||
UpdateVCN(ctx context.Context, cred Credentials, region, vcnID, displayName string) (VCN, error)
|
||
DeleteVCN(ctx context.Context, cred Credentials, region, vcnID string) error
|
||
EnableVCNIPv6(ctx context.Context, cred Credentials, region, vcnID string) ([]IPv6Step, error)
|
||
// 子网增删改查。
|
||
ListSubnets(ctx context.Context, cred Credentials, region, vcnID string) ([]Subnet, error)
|
||
CreateSubnet(ctx context.Context, cred Credentials, in CreateSubnetInput) (Subnet, error)
|
||
GetSubnet(ctx context.Context, cred Credentials, region, subnetID string) (Subnet, error)
|
||
UpdateSubnet(ctx context.Context, cred Credentials, region, subnetID, displayName string) (Subnet, error)
|
||
DeleteSubnet(ctx context.Context, cred Credentials, region, subnetID string) error
|
||
// 安全列表增删改查。
|
||
ListSecurityLists(ctx context.Context, cred Credentials, region, vcnID string) ([]SecurityList, error)
|
||
CreateSecurityList(ctx context.Context, cred Credentials, in CreateSecurityListInput) (SecurityList, error)
|
||
GetSecurityList(ctx context.Context, cred Credentials, region, securityListID string) (SecurityList, error)
|
||
UpdateSecurityList(ctx context.Context, cred Credentials, securityListID string, in UpdateSecurityListInput) (SecurityList, error)
|
||
DeleteSecurityList(ctx context.Context, cred Credentials, region, securityListID string) error
|
||
// ListAvailabilityDomains 列出区域内可用域,创建实例的前置查询。
|
||
ListAvailabilityDomains(ctx context.Context, cred Credentials, region string) ([]string, error)
|
||
// 实例增删改查与电源操作。
|
||
ListInstances(ctx context.Context, cred Credentials, region string) ([]Instance, error)
|
||
LaunchInstance(ctx context.Context, cred Credentials, in CreateInstanceInput) (Instance, error)
|
||
GetInstance(ctx context.Context, cred Credentials, region, instanceID string) (Instance, error)
|
||
UpdateInstance(ctx context.Context, cred Credentials, instanceID string, in UpdateInstanceInput) (Instance, error)
|
||
TerminateInstance(ctx context.Context, cred Credentials, region, instanceID string, preserveBootVolume bool) error
|
||
InstanceAction(ctx context.Context, cred Credentials, region, instanceID, action string) (Instance, error)
|
||
// 引导卷查删改;引导卷由创建实例产生,不单独创建。
|
||
ListBootVolumes(ctx context.Context, cred Credentials, region, availabilityDomain string) ([]BootVolume, error)
|
||
GetBootVolume(ctx context.Context, cred Credentials, region, bootVolumeID string) (BootVolume, error)
|
||
UpdateBootVolume(ctx context.Context, cred Credentials, bootVolumeID string, in UpdateBootVolumeInput) (BootVolume, error)
|
||
DeleteBootVolume(ctx context.Context, cred Credentials, region, bootVolumeID string) error
|
||
// 实例 IP:更换主 VNIC 临时公网 IP、添加与取消分配 IPv6 地址。
|
||
ChangeInstancePublicIP(ctx context.Context, cred Credentials, region, instanceID string) (string, error)
|
||
AddInstanceIpv6(ctx context.Context, cred Credentials, region, instanceID, address string) (string, error)
|
||
DeleteInstanceIpv6(ctx context.Context, cred Credentials, region, instanceID, address string) error
|
||
// VNIC 管理:列出实例网卡、附加次要网卡、分离(主网卡由 OCI 拒绝)、按网卡加 IPv6。
|
||
ListInstanceVnics(ctx context.Context, cred Credentials, region, instanceID string) ([]Vnic, error)
|
||
AttachVnic(ctx context.Context, cred Credentials, region, instanceID string, in AttachVnicInput) (Vnic, error)
|
||
DetachVnic(ctx context.Context, cred Credentials, region, attachmentID string) error
|
||
AddVnicIpv6(ctx context.Context, cred Credentials, region, vnicID, address string) (string, error)
|
||
// GenAI 网关:区域模型列表、聊天(非流式/流式)、配额探测、文本向量化。
|
||
ListGenAiModels(ctx context.Context, cred Credentials, region string) ([]GenAiModel, error)
|
||
GenAiProbeChat(ctx context.Context, cred Credentials, region, modelOcid, modelName string) (int, error)
|
||
GenAiEmbed(ctx context.Context, cred Credentials, region, modelOcid string, inputs []string, dimensions *int) ([][]float32, *aiwire.Usage, error)
|
||
// GenAiCompatResponses 直通 OpenAI Responses 请求体到 /actions/v1/responses(xAI 服务端工具通路)。
|
||
GenAiCompatResponses(ctx context.Context, cred Credentials, region string, body []byte) ([]byte, error)
|
||
// GenAiCompatResponsesStream 流式直通 /actions/v1/responses,建立成功返回 SSE body。
|
||
GenAiCompatResponsesStream(ctx context.Context, cred Credentials, region string, body []byte) (io.ReadCloser, error)
|
||
// GenAiCompatSpeech 直通 OpenAI Audio Speech 请求体到 /openai/v1/audio/speech,返回音频与 Content-Type。
|
||
GenAiCompatSpeech(ctx context.Context, cred Credentials, region string, body []byte) ([]byte, string, error)
|
||
// GenAiRerank 文档重排,返回按相关度排序的下标与得分。
|
||
GenAiRerank(ctx context.Context, cred Credentials, region, modelOcid, query string, documents []string, topN *int) ([]RerankRank, error)
|
||
// GenAiApplyGuardrails 对单条文本执行内容审核 / PII / 提示注入检测。
|
||
GenAiApplyGuardrails(ctx context.Context, cred Credentials, region, text string) (*GuardrailsOutcome, error)
|
||
// 控制台连接:创建(VNC/串口连接串)、列出、删除。
|
||
CreateConsoleConnection(ctx context.Context, cred Credentials, region, instanceID, sshPublicKey string) (ConsoleConnection, error)
|
||
ListConsoleConnections(ctx context.Context, cred Credentials, region, instanceID string) ([]ConsoleConnection, error)
|
||
DeleteConsoleConnection(ctx context.Context, cred Credentials, region, connectionID string) error
|
||
// 引导卷挂载:列出、挂载、分离、替换。
|
||
ListBootVolumeAttachments(ctx context.Context, cred Credentials, region, instanceID string) ([]BootVolumeAttachment, error)
|
||
AttachBootVolume(ctx context.Context, cred Credentials, region, instanceID, bootVolumeID string) (BootVolumeAttachment, error)
|
||
DetachBootVolume(ctx context.Context, cred Credentials, region, attachmentID string) error
|
||
ReplaceBootVolume(ctx context.Context, cred Credentials, region, instanceID, newBootVolumeID string) (BootVolumeAttachment, error)
|
||
// 块卷挂载:列出块卷、列出挂载、附加、分离。
|
||
ListBlockVolumes(ctx context.Context, cred Credentials, region, availabilityDomain string) ([]BlockVolume, error)
|
||
ListVolumeAttachments(ctx context.Context, cred Credentials, region, instanceID string) ([]VolumeAttachment, error)
|
||
AttachVolume(ctx context.Context, cred Credentials, region, instanceID, volumeID string, readOnly bool) (VolumeAttachment, error)
|
||
DetachVolume(ctx context.Context, cred Credentials, region, attachmentID string) error
|
||
// 租户观测:实例流量统计、成本分析。
|
||
SummarizeInstanceTraffic(ctx context.Context, cred Credentials, q TrafficQuery) (InstanceTraffic, error)
|
||
SummarizeCosts(ctx context.Context, cred Credentials, q CostQuery) ([]CostItem, error)
|
||
// ListAuditEvents 实时查询租户根 compartment 的审计事件(最多翻 maxAuditPages 页,
|
||
// 到限截断并回传续查游标);page 传上次结果的 NextPage 可从断点续翻。
|
||
// 批式懒加载走 ListAuditEventsBatch,本方法保留给详情小窗反查。
|
||
ListAuditEvents(ctx context.Context, cred Credentials, region string, start, end time.Time, page string) (AuditEventsResult, error)
|
||
// ListAuditEventsBatch 从游标位置向更早方向收集约 limit 条保留事件,
|
||
// 分窗回溯(空窗倍增),跨批不重不漏;到 365 天保留期尽头置 Exhausted。
|
||
ListAuditEventsBatch(ctx context.Context, cred Credentials, region string, cur AuditCursor, limit int) (AuditBatchResult, error)
|
||
// 身份域:租户 ACTIVE 域枚举(多域租户选择器数据源)。
|
||
ListIdentityDomains(ctx context.Context, cred Credentials, region string) ([]IdentityDomain, error)
|
||
// 账户能力:暂停标记/账户状态/免费层信息(对暂停租户仍可访问,测活据此判「暂停」)。
|
||
GetAccountCapabilities(ctx context.Context, cred Credentials, region string) (AccountCapabilities, error)
|
||
// 租户用户管理:domainID 非空按域走 Identity Domains SCIM,
|
||
// 为空保持经典 IAM 为主、新式租户自动回退的旧行为。
|
||
ListTenantUsers(ctx context.Context, cred Credentials, homeRegion, domainID string) ([]TenantUser, error)
|
||
GetTenantUserDetail(ctx context.Context, cred Credentials, homeRegion, domainID, userID string) (TenantUserDetail, error)
|
||
CreateTenantUser(ctx context.Context, cred Credentials, homeRegion, domainID string, in CreateTenantUserInput) (TenantUser, error)
|
||
UpdateTenantUser(ctx context.Context, cred Credentials, homeRegion, domainID, userID string, in UpdateTenantUserInput) (TenantUser, error)
|
||
DeleteTenantUser(ctx context.Context, cred Credentials, homeRegion, domainID, userID string) error
|
||
ResetTenantUserPassword(ctx context.Context, cred Credentials, homeRegion, domainID, userID string) (string, error)
|
||
DeleteTenantUserMfaDevices(ctx context.Context, cred Credentials, homeRegion, domainID, userID string) (int, error)
|
||
DeleteTenantUserApiKeys(ctx context.Context, cred Credentials, homeRegion, userID string, includeCurrent bool) (int, error)
|
||
// 域设置:通知收件人、密码策略、身份设置。
|
||
GetNotificationRecipients(ctx context.Context, cred Credentials, region, domainID string) (NotificationRecipients, error)
|
||
UpdateNotificationRecipients(ctx context.Context, cred Credentials, region, domainID string, emails []string) (NotificationRecipients, error)
|
||
ListPasswordPolicies(ctx context.Context, cred Credentials, region, domainID string) ([]PasswordPolicyInfo, error)
|
||
UpdatePasswordPolicy(ctx context.Context, cred Credentials, region, domainID, policyID string, in UpdatePasswordPolicyInput) (PasswordPolicyInfo, error)
|
||
GetIdentitySetting(ctx context.Context, cred Credentials, region, domainID string) (IdentitySettingInfo, error)
|
||
UpdateIdentitySetting(ctx context.Context, cred Credentials, region, domainID string, primaryEmailRequired bool) (IdentitySettingInfo, error)
|
||
// Federation:SAML IdP 管理、域 SP 元数据、sign-on 免 MFA 规则。
|
||
ListIdentityProviders(ctx context.Context, cred Credentials, region, domainID string) ([]IdentityProviderInfo, error)
|
||
CreateSamlIdentityProvider(ctx context.Context, cred Credentials, region, domainID string, in CreateIdpInput) (IdentityProviderInfo, error)
|
||
SetIdentityProviderEnabled(ctx context.Context, cred Credentials, region, domainID, idpID string, enabled bool) (IdentityProviderInfo, error)
|
||
DeleteIdentityProvider(ctx context.Context, cred Credentials, region, domainID, idpID string) error
|
||
DownloadDomainSamlMetadata(ctx context.Context, cred Credentials, region, domainID string) ([]byte, error)
|
||
ListConsoleSignOnRules(ctx context.Context, cred Credentials, region, domainID string) ([]SignOnRuleInfo, error)
|
||
CreateMfaExemptionRule(ctx context.Context, cred Credentials, region, domainID, idpID, ruleName string) (SignOnRuleInfo, error)
|
||
DeleteMfaExemptionRule(ctx context.Context, cred Credentials, region, domainID, ruleID string) error
|
||
// 日志回传链路(方案A):Topic/CUSTOM_HTTPS 订阅/IAM Policy/Service Connector
|
||
// 的幂等创建、状态聚合与销毁;IAM 写操作发往 homeRegion。
|
||
EnsureRelayTopic(ctx context.Context, cred Credentials) (RelayResource, error)
|
||
EnsureRelaySubscription(ctx context.Context, cred Credentials, topicID, endpoint string) (RelayResource, error)
|
||
GetRelaySubscription(ctx context.Context, cred Credentials, subscriptionID string) (RelayResource, error)
|
||
EnsureRelayPolicy(ctx context.Context, cred Credentials, homeRegion string) (RelayResource, error)
|
||
EnsureRelayConnector(ctx context.Context, cred Credentials, topicID, condition string) (RelayResource, error)
|
||
RelayState(ctx context.Context, cred Credentials, endpoint string) (RelayState, error)
|
||
DeleteRelayConnector(ctx context.Context, cred Credentials, connectorID string) error
|
||
DeleteRelayPolicy(ctx context.Context, cred Credentials, homeRegion, policyID string) error
|
||
DeleteRelaySubscription(ctx context.Context, cred Credentials, subscriptionID string) error
|
||
DeleteRelayTopic(ctx context.Context, cred Credentials, topicID string) error
|
||
}
|
||
|
||
// RealClient 基于官方 SDK 实现 Client。
|
||
type RealClient struct {
|
||
limitDefs sync.Map // tenancy|region|service → limitDefEntry,配额定义预筛缓存
|
||
shapes sync.Map // tenancy|region|ad → shapeCacheEntry,shape 清单缓存
|
||
}
|
||
|
||
// NewClient 返回生产使用的真实客户端。
|
||
func NewClient() *RealClient { return &RealClient{} }
|
||
|
||
func provider(cred Credentials) common.ConfigurationProvider {
|
||
var pass *string
|
||
if cred.Passphrase != "" {
|
||
pass = &cred.Passphrase
|
||
}
|
||
return common.NewRawConfigurationProvider(
|
||
cred.TenancyOCID, cred.UserOCID, cred.Region,
|
||
cred.Fingerprint, cred.PrivateKey, pass,
|
||
)
|
||
}
|
||
|
||
// normalizeRegion 兼容区域短名:iad → us-ashburn-1;空值与全名原样返回。
|
||
// 实例等资源对象上的 region 字段是短名,直接透传时借此归一。
|
||
func normalizeRegion(region string) string {
|
||
if region == "" {
|
||
return ""
|
||
}
|
||
return string(common.StringToRegion(region))
|
||
}
|
||
|
||
// ValidateKey 实现 Client。
|
||
func (c *RealClient) ValidateKey(ctx context.Context, cred Credentials) (TenancyInfo, error) {
|
||
ic, err := identity.NewIdentityClientWithConfigurationProvider(provider(cred))
|
||
if err != nil {
|
||
return TenancyInfo{}, fmt.Errorf("new identity client: %w", err)
|
||
}
|
||
applyProxy(&ic.BaseClient, cred)
|
||
resp, err := ic.GetTenancy(ctx, identity.GetTenancyRequest{TenancyId: &cred.TenancyOCID})
|
||
if err != nil {
|
||
return TenancyInfo{}, fmt.Errorf("get tenancy: %w", err)
|
||
}
|
||
return TenancyInfo{
|
||
Name: deref(resp.Name),
|
||
HomeRegionKey: deref(resp.HomeRegionKey),
|
||
}, nil
|
||
}
|
||
|
||
// FetchAccountProfile 实现 Client:从订阅列表选中 CLOUDCM,
|
||
// 再查询完整订阅并按付费模式、促销状态判定账户类别。
|
||
func (c *RealClient) FetchAccountProfile(ctx context.Context, cred Credentials) (AccountProfile, error) {
|
||
subs, err := c.ListSubscriptions(ctx, cred)
|
||
if err != nil {
|
||
return AccountProfile{}, err
|
||
}
|
||
subID := ""
|
||
for _, sub := range subs {
|
||
if strings.EqualFold(sub.ServiceName, cloudcmServiceName) {
|
||
subID = sub.ID
|
||
break
|
||
}
|
||
}
|
||
if subID == "" {
|
||
return AccountProfile{}, ErrNoCloudcmSubscription
|
||
}
|
||
detail, err := c.GetSubscription(ctx, cred, subID)
|
||
if err != nil {
|
||
return AccountProfile{}, err
|
||
}
|
||
return buildAccountProfile(detail), nil
|
||
}
|
||
|
||
func buildAccountProfile(detail SubscriptionDetail) AccountProfile {
|
||
p := AccountProfile{
|
||
SubscriptionID: detail.ID,
|
||
ServiceName: detail.ServiceName,
|
||
PaymentModel: detail.PaymentModel,
|
||
SubscriptionTier: detail.SubscriptionTier,
|
||
}
|
||
p.PromotionStatus, p.PromotionAmount, p.PromotionExpires = pickPromotion(detail.Promotions)
|
||
p.AccountType = ClassifyAccount(p.PaymentModel, p.SubscriptionTier, p.PromotionStatus)
|
||
return p
|
||
}
|
||
|
||
// pickPromotion 从多条促销记录中选出最能代表当前状态的一条:
|
||
// 优先 ACTIVE,其次 EXPIRED,否则回退第一条。
|
||
func pickPromotion(promos []PromotionInfo) (string, float32, *time.Time) {
|
||
best, bestRank := -1, -1
|
||
for i, p := range promos {
|
||
if rank := promotionRank(p.Status); rank > bestRank {
|
||
best, bestRank = i, rank
|
||
}
|
||
}
|
||
if best < 0 {
|
||
return "", 0, nil
|
||
}
|
||
p := promos[best]
|
||
return p.Status, p.Amount, p.TimeExpired
|
||
}
|
||
|
||
func promotionRank(status string) int {
|
||
switch strings.ToUpper(status) {
|
||
case "ACTIVE":
|
||
return 2
|
||
case "EXPIRED":
|
||
return 1
|
||
default:
|
||
return 0
|
||
}
|
||
}
|
||
|
||
func deref(s *string) string {
|
||
if s != nil {
|
||
return *s
|
||
}
|
||
return ""
|
||
}
|
||
|
||
// orEmpty 把 nil 切片归一为空切片:JSON 序列化输出 [] 而非 null,
|
||
// 避免前端对 null 调用 .length / .map / .join 崩溃。
|
||
func orEmpty[T any](s []T) []T {
|
||
if s == nil {
|
||
return []T{}
|
||
}
|
||
return s
|
||
}
|