Files
simstudioai--sim/apps/sim/providers/bedrock/utils.ts
T
wehub-resource-sync d25d482dc2
Publish CLI Package / publish-npm (push) Waiting to run
Publish Python SDK / publish-pypi (push) Waiting to run
Publish TypeScript SDK / publish-npm (push) Waiting to run
CI / Migrate Dev DB (push) Has been skipped
CI / Detect Version (push) Has been cancelled
CI / Migrate DB (push) Has been cancelled
CI / Build Dev ECR (./docker/app.Dockerfile, ECR_APP) (push) Has been cancelled
CI / Build Dev ECR (./docker/db.Dockerfile, ECR_MIGRATIONS) (push) Has been cancelled
CI / Build Dev ECR (./docker/pii.Dockerfile, ECR_PII) (push) Has been cancelled
CI / Build Dev ECR (./docker/realtime.Dockerfile, ECR_REALTIME) (push) Has been cancelled
CI / Deploy Trigger.dev (Dev) (push) Has been cancelled
CI / Build AMD64 (./docker/app.Dockerfile, ECR_APP, ghcr.io/simstudioai/simstudio) (push) Has been cancelled
CI / Build AMD64 (./docker/db.Dockerfile, ECR_MIGRATIONS, ghcr.io/simstudioai/migrations) (push) Has been cancelled
CI / Build AMD64 (./docker/pii.Dockerfile, ECR_PII, ghcr.io/simstudioai/pii) (push) Has been cancelled
CI / Build AMD64 (./docker/realtime.Dockerfile, ECR_REALTIME, ghcr.io/simstudioai/realtime) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/app.Dockerfile, ghcr.io/simstudioai/simstudio) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/db.Dockerfile, ghcr.io/simstudioai/migrations) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/pii.Dockerfile, ghcr.io/simstudioai/pii) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/realtime.Dockerfile, ghcr.io/simstudioai/realtime) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/migrations) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/pii) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/realtime) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/simstudio) (push) Has been cancelled
CI / Check Docs Changes (push) Has been cancelled
CI / Process Docs (push) Has been cancelled
CI / Create GitHub Release (push) Has been cancelled
CI / Test and Build (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 13:20:55 +08:00

144 lines
4.8 KiB
TypeScript

import type { ConverseStreamOutput } from '@aws-sdk/client-bedrock-runtime'
import { createLogger } from '@sim/logger'
import { randomFloat } from '@sim/utils/random'
import { trackForcedToolUsage } from '@/providers/utils'
const logger = createLogger('BedrockUtils')
export interface BedrockStreamUsage {
inputTokens: number
outputTokens: number
}
export function createReadableStreamFromBedrockStream(
bedrockStream: AsyncIterable<ConverseStreamOutput>,
onComplete?: (content: string, usage: BedrockStreamUsage) => void
): ReadableStream<Uint8Array> {
let fullContent = ''
let inputTokens = 0
let outputTokens = 0
return new ReadableStream({
async start(controller) {
try {
for await (const event of bedrockStream) {
if (event.contentBlockDelta?.delta?.text) {
const text = event.contentBlockDelta.delta.text
fullContent += text
controller.enqueue(new TextEncoder().encode(text))
} else if (event.metadata?.usage) {
inputTokens = event.metadata.usage.inputTokens ?? 0
outputTokens = event.metadata.usage.outputTokens ?? 0
}
}
if (onComplete) {
onComplete(fullContent, { inputTokens, outputTokens })
}
controller.close()
} catch (err) {
controller.error(err)
}
},
})
}
export function checkForForcedToolUsage(
toolUseBlocks: Array<{ name: string }>,
toolChoice: any,
forcedTools: string[],
usedForcedTools: string[]
): { hasUsedForcedTool: boolean; usedForcedTools: string[] } | null {
if (typeof toolChoice === 'object' && toolChoice !== null && toolUseBlocks.length > 0) {
const adaptedToolCalls = toolUseBlocks.map((tool) => ({ name: tool.name }))
const adaptedToolChoice = toolChoice.tool
? { function: { name: toolChoice.tool.name } }
: toolChoice
return trackForcedToolUsage(
adaptedToolCalls,
adaptedToolChoice,
logger,
'bedrock',
forcedTools,
usedForcedTools
)
}
return null
}
/**
* Generates a unique tool use ID for Bedrock.
* AWS Bedrock requires toolUseId to be 1-64 characters, pattern [a-zA-Z0-9_-]+
*/
export function generateToolUseId(toolName: string): string {
const timestamp = Date.now().toString(36) // Base36 timestamp (9 chars)
const random = randomFloat().toString(36).substring(2, 7) // 5 random chars
const suffix = `-${timestamp}-${random}` // ~15 chars
const maxNameLength = 64 - suffix.length
const truncatedName = toolName.substring(0, maxNameLength).replace(/[^a-zA-Z0-9_-]/g, '_')
return `${truncatedName}${suffix}`
}
/**
* Models whose AWS model cards state geo/cross-region inference profiles are
* not supported ("Geo inference ID: Not supported"). These must be invoked
* with the bare in-region model ID — prefixing them with a geo profile
* (e.g. us.mistral...) produces an invalid model identifier.
*/
const GEO_PROFILE_UNSUPPORTED_MODEL_IDS = new Set([
'mistral.mistral-large-3-675b-instruct',
'mistral.mistral-large-2411-v1:0',
'mistral.mistral-large-2407-v1:0',
'mistral.magistral-small-2509',
'mistral.ministral-3-14b-instruct',
'mistral.ministral-3-8b-instruct',
'mistral.ministral-3-3b-instruct',
'mistral.mixtral-8x7b-instruct-v0:1',
'amazon.titan-text-premier-v1:0',
'cohere.command-r-v1:0',
'cohere.command-r-plus-v1:0',
])
/**
* Converts a model ID to the Bedrock inference profile format.
* AWS Bedrock requires inference profile IDs (e.g., us.anthropic.claude-...)
* for on-demand invocation of newer models, while some models only accept
* the bare in-region model ID.
*
* @param modelId - The model ID (e.g., "bedrock/anthropic.claude-sonnet-4-5-20250929-v1:0")
* @param region - The AWS region (e.g., "us-east-1")
* @returns The inference profile ID (e.g., "us.anthropic.claude-sonnet-4-5-20250929-v1:0")
*/
export function getBedrockInferenceProfileId(modelId: string, region: string): string {
const baseModelId = modelId.startsWith('bedrock/') ? modelId.slice(8) : modelId
if (/^(us-gov|us|eu|apac|au|ca|jp|global)\./.test(baseModelId)) {
return baseModelId
}
if (GEO_PROFILE_UNSUPPORTED_MODEL_IDS.has(baseModelId)) {
return baseModelId
}
let inferencePrefix: string
if (region.startsWith('us-gov-')) {
inferencePrefix = 'us-gov'
} else if (region.startsWith('us-') || region.startsWith('ca-')) {
inferencePrefix = 'us'
} else if (region.startsWith('eu-') || region === 'il-central-1') {
inferencePrefix = 'eu'
} else if (region.startsWith('ap-') || region.startsWith('me-')) {
inferencePrefix = 'apac'
} else if (region.startsWith('sa-')) {
inferencePrefix = 'us'
} else if (region.startsWith('af-')) {
inferencePrefix = 'eu'
} else {
inferencePrefix = 'us'
}
return `${inferencePrefix}.${baseModelId}`
}