Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions src/services/api/claude.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1360,6 +1360,12 @@ async function* queryModel(
return
}

if (getAPIProvider() === 'minimax') {
const { queryModelMiniMax } = await import('./minimax/index.js')
yield* queryModelMiniMax(messagesForAPI, systemPrompt, filteredTools, signal, options)
return
}

// Instrumentation: Track message count after normalization
logEvent('tengu_api_after_normalize', {
postNormalizedMessageCount: messagesForAPI.length,
Expand Down
90 changes: 90 additions & 0 deletions src/services/api/minimax/__tests__/client.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
import { describe, expect, test, beforeEach, afterEach, mock } from 'bun:test'

// Defensive: mock proxy module before importing
mock.module('src/utils/proxy.js', () => ({
getProxyFetchOptions: () => ({} as any),
}))

import { getMiniMaxClient, clearMiniMaxClientCache } from '../client.js'

describe('getMiniMaxClient', () => {
const originalEnv = { ...process.env }

beforeEach(() => {
clearMiniMaxClientCache()
process.env.MINIMAX_API_KEY = 'test-minimax-key'
delete process.env.MINIMAX_BASE_URL
})

afterEach(() => {
clearMiniMaxClientCache()
process.env = { ...originalEnv }
})

test('creates client with default base URL', () => {
const client = getMiniMaxClient()
expect(client).toBeDefined()
expect(client.baseURL).toBe('https://api.minimax.io/anthropic')
})

test('uses MINIMAX_BASE_URL when set', () => {
process.env.MINIMAX_BASE_URL = 'https://custom.minimax.api/anthropic'
clearMiniMaxClientCache()
const client = getMiniMaxClient()
expect(client.baseURL).toBe('https://custom.minimax.api/anthropic')
})

test('default base URL does not use api.minimax.chat', () => {
const client = getMiniMaxClient()
expect(client.baseURL).not.toContain('api.minimax.chat')
expect(client.baseURL).toContain('api.minimax.io')
})

test('returns cached client on second call', () => {
const client1 = getMiniMaxClient()
const client2 = getMiniMaxClient()
expect(client1).toBe(client2)
})

test('clearMiniMaxClientCache resets cache', () => {
const client1 = getMiniMaxClient()
clearMiniMaxClientCache()
process.env.MINIMAX_BASE_URL = 'https://other.minimax.api/anthropic'
const client2 = getMiniMaxClient()
expect(client1).not.toBe(client2)
})
})

describe('MiniMax API constraints', () => {
test('default base URL uses overseas api.minimax.io (not api.minimax.chat)', () => {
const defaultBaseUrl = 'https://api.minimax.io/anthropic'
expect(defaultBaseUrl).toContain('api.minimax.io')
expect(defaultBaseUrl).not.toContain('api.minimax.chat')
})

test('validates temperature range (0.0, 1.0] — 0 is invalid for MiniMax', () => {
const isValidTemperature = (t: number) => t > 0 && t <= 1.0
expect(isValidTemperature(1.0)).toBe(true)
expect(isValidTemperature(0.5)).toBe(true)
expect(isValidTemperature(0.0)).toBe(false)
expect(isValidTemperature(1.1)).toBe(false)
})

test('filters unsupported parameters', () => {
const UNSUPPORTED_PARAMS = new Set(['top_k', 'stop_sequences', 'service_tier'])
const input: Record<string, unknown> = {
model: 'MiniMax-M2.7',
messages: [{ role: 'user', content: 'hi' }],
top_k: 40,
stop_sequences: ['END'],
temperature: 1.0,
}
const filtered = Object.fromEntries(
Object.entries(input).filter(([k]) => !UNSUPPORTED_PARAMS.has(k)),
)
expect('top_k' in filtered).toBe(false)
expect('stop_sequences' in filtered).toBe(false)
expect('temperature' in filtered).toBe(true)
expect('model' in filtered).toBe(true)
})
})
43 changes: 43 additions & 0 deletions src/services/api/minimax/client.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import Anthropic from '@anthropic-ai/sdk'
import { getProxyFetchOptions } from 'src/utils/proxy.js'

/**
* Environment variables:
*
* MINIMAX_API_KEY: Required. API key for the MiniMax Anthropic-compatible endpoint.
* MINIMAX_BASE_URL: Optional. Defaults to https://api.minimax.io/anthropic.
*/

const DEFAULT_BASE_URL = 'https://api.minimax.io/anthropic'

let cachedClient: Anthropic | null = null

export function getMiniMaxClient(options?: {
maxRetries?: number
fetchOverride?: typeof fetch
}): Anthropic {
if (cachedClient) return cachedClient

const apiKey = process.env.MINIMAX_API_KEY || ''
const baseURL = process.env.MINIMAX_BASE_URL || DEFAULT_BASE_URL

const client = new Anthropic({
apiKey,
baseURL,
maxRetries: options?.maxRetries ?? 0,
timeout: parseInt(process.env.API_TIMEOUT_MS || String(600 * 1000), 10),
dangerouslyAllowBrowser: true,
fetchOptions: getProxyFetchOptions({ forAnthropicAPI: false }),
...(options?.fetchOverride && { fetch: options.fetchOverride }),
})

if (!options?.fetchOverride) {
cachedClient = client
}

return client
}

export function clearMiniMaxClientCache(): void {
cachedClient = null
}
220 changes: 220 additions & 0 deletions src/services/api/minimax/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,220 @@
import type { BetaToolUnion } from '@anthropic-ai/sdk/resources/beta/messages/messages.mjs'
import type { SystemPrompt } from '../../../utils/systemPromptType.js'
import type { Message, StreamEvent, SystemAPIErrorMessage, AssistantMessage } from '../../../types/message.js'
import type { Tools } from '../../../Tool.js'
import { getMiniMaxClient } from './client.js'
import { normalizeMessagesForAPI } from '../../../utils/messages.js'
import type { SDKAssistantMessageError } from '../../../entrypoints/agentSdkTypes.js'
import { toolToAPISchema } from '../../../utils/api.js'
import { logForDebugging } from '../../../utils/debug.js'
import { addToTotalSessionCost } from '../../../cost-tracker.js'
import { calculateUSDCost } from '../../../utils/modelCost.js'
import { recordLLMObservation } from '../../../services/langfuse/tracing.js'
import { convertMessagesToLangfuse, convertOutputToLangfuse, convertToolsToLangfuse } from '../../../services/langfuse/convert.js'
import type { Options } from '../claude.js'
import { randomUUID } from 'crypto'
import {
createAssistantAPIErrorMessage,
normalizeContentFromAPI,
} from '../../../utils/messages.js'
Comment on lines +1 to +19
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion | 🟠 Major

Use src/* path alias instead of deep relative imports.

All relative imports here (../../../utils/systemPromptType.js, ../../../types/message.js, ../../../Tool.js, etc.) violate the repository convention.

As per coding guidelines: "Import from src/* path alias instead of relative paths (e.g., import { ... } from 'src/utils/...')".

♻️ Example rewrite
-import type { SystemPrompt } from '../../../utils/systemPromptType.js'
-import type { Message, StreamEvent, SystemAPIErrorMessage, AssistantMessage } from '../../../types/message.js'
-import type { Tools } from '../../../Tool.js'
+import type { SystemPrompt } from 'src/utils/systemPromptType.js'
+import type { Message, StreamEvent, SystemAPIErrorMessage, AssistantMessage } from 'src/types/message.js'
+import type { Tools } from 'src/Tool.js'

Apply the same rewrite to the remaining relative imports on lines 7, 9–13, and 17–19.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/services/api/minimax/index.ts` around lines 1 - 19, Replace the deep
relative imports in this module with the repository path alias "src/*": update
imports that reference '../../../utils/systemPromptType.js',
'../../../types/message.js', '../../../Tool.js', './client.js'
(getMiniMaxClient), '../../../utils/messages.js' (normalizeMessagesForAPI,
createAssistantAPIErrorMessage, normalizeContentFromAPI),
'../../../entrypoints/agentSdkTypes.js' (SDKAssistantMessageError),
'../../../utils/api.js' (toolToAPISchema), '../../../utils/debug.js'
(logForDebugging), '../../../cost-tracker.js' (addToTotalSessionCost),
'../../../utils/modelCost.js' (calculateUSDCost),
'../../../services/langfuse/tracing.js' (recordLLMObservation),
'../../../services/langfuse/convert.js' (convertMessagesToLangfuse,
convertOutputToLangfuse, convertToolsToLangfuse), and '../claude.js' (Options)
to use imports like 'src/utils/...' or 'src/services/...' per project convention
so the module consistently uses the src/* alias instead of ../../.. paths.


// Parameters not supported by MiniMax's Anthropic-compatible API
const UNSUPPORTED_PARAMS = new Set([
'top_k',
'stop_sequences',
'service_tier',
'mcp_servers',
'context_management',
'container',
])

/**
* MiniMax query path. MiniMax uses an Anthropic-compatible API, so we use
* the Anthropic SDK directly with a MiniMax base URL.
*
* Key constraints:
* - temperature must be in (0.0, 1.0] — 0.0 is invalid
* - Some Anthropic-specific parameters are unsupported
* - System message is accepted as a string
*/
export async function* queryModelMiniMax(
messages: Message[],
systemPrompt: SystemPrompt,
tools: Tools,
signal: AbortSignal,
options: Options,
): AsyncGenerator<
StreamEvent | AssistantMessage | SystemAPIErrorMessage,
void
> {
try {
const messagesForAPI = normalizeMessagesForAPI(messages, tools)

const toolSchemas = await Promise.all(
tools.map(tool =>
toolToAPISchema(tool, {
getToolPermissionContext: options.getToolPermissionContext,
tools,
agents: options.agents,
allowedAgentTypes: options.allowedAgentTypes,
model: options.model,
}),
),
)

// Filter out unsupported tool types (computer use, etc.)
const standardTools = toolSchemas.filter(
(t): t is BetaToolUnion & { type: string } => {
const anyT = t as unknown as Record<string, unknown>
return anyT.type !== 'advisor_20260301' && anyT.type !== 'computer_20250124'
},
)

// Join system prompt blocks into a single string
const systemText = systemPrompt.filter(Boolean).join('\n\n')

// MiniMax temperature constraint: must be in (0.0, 1.0], default 1.0
const temperature =
options.temperatureOverride !== undefined && options.temperatureOverride > 0
? options.temperatureOverride
: 1.0

const client = getMiniMaxClient({
maxRetries: 0,
fetchOverride: options.fetchOverride as typeof fetch | undefined,
})

logForDebugging(
`[MiniMax] Calling model=${options.model}, messages=${messagesForAPI.length}, tools=${standardTools.length}`,
)

const stream = client.messages.stream(
{
model: options.model,
messages: messagesForAPI as Parameters<typeof client.messages.stream>[0]['messages'],
...(systemText && { system: systemText }),
...(standardTools.length > 0 && {
tools: standardTools as Parameters<typeof client.messages.stream>[0]['tools'],
}),
max_tokens: options.maxTokens ?? 16000,
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Confirm Options type fields and how peer adapters resolve max_tokens.
rg -nP --type=ts -C2 '\bmaxTokens\b|\bmaxOutputTokensOverride\b' src/services/api
ast-grep --pattern $'export type Options = {
  $$$
}'

Repository: claude-code-best/claude-code

Length of output: 10657


🏁 Script executed:

cat -n src/services/api/minimax/index.ts

Repository: claude-code-best/claude-code

Length of output: 9413


🏁 Script executed:

rg -n 'getMaxOutputTokensForModel' src/services/api/claude.ts

Repository: claude-code-best/claude-code

Length of output: 197


Bug: options.maxTokens does not exist on Options — user overrides silently ignored.

The Options type in src/services/api/claude.ts exposes maxOutputTokensOverride, not maxTokens. As written, options.maxTokens is always undefined and every MiniMax request is hard-capped at 16000, ignoring CLAUDE_CODE_MAX_OUTPUT_TOKENS and any caller override. The OpenAI adapter demonstrates the correct pattern.

🐛 Suggested fix
-        max_tokens: options.maxTokens ?? 16000,
+        max_tokens:
+          options.maxOutputTokensOverride ??
+          getMaxOutputTokensForModel(options.model),

Add the import:

import { getMaxOutputTokensForModel } from '../claude.js'
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
max_tokens: options.maxTokens ?? 16000,
max_tokens:
options.maxOutputTokensOverride ??
getMaxOutputTokensForModel(options.model),
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/services/api/minimax/index.ts` at line 99, Replace the incorrect use of
options.maxTokens (which doesn't exist) with the proper logic that computes the
max_tokens via getMaxOutputTokensForModel and the Options field
maxOutputTokensOverride: import getMaxOutputTokensForModel from ../claude.js,
then set max_tokens to options.maxOutputTokensOverride ??
getMaxOutputTokensForModel(options.model) (or similar helper used elsewhere), so
callers and CLAUDE_CODE_MAX_OUTPUT_TOKENS are respected instead of always
falling back to 16000; update the code that currently assigns max_tokens to use
these symbols (Options, maxOutputTokensOverride, getMaxOutputTokensForModel,
max_tokens).

temperature,
stream: true,
},
{ signal },
)

const contentBlocks: Record<number, any> = {}
const collectedMessages: AssistantMessage[] = []
let partialMessage: any = undefined
let usage = {
input_tokens: 0,
output_tokens: 0,
cache_creation_input_tokens: 0,
cache_read_input_tokens: 0,
}
let ttftMs = 0
const start = Date.now()

for await (const event of stream) {
switch (event.type) {
case 'message_start': {
partialMessage = (event as any).message
ttftMs = Date.now() - start
if ((event as any).message?.usage) {
usage = { ...usage, ...((event as any).message.usage) }
}
break
}
case 'content_block_start': {
const idx = (event as any).index
const cb = (event as any).content_block
if (cb.type === 'tool_use') {
contentBlocks[idx] = { ...cb, input: '' }
} else if (cb.type === 'text') {
contentBlocks[idx] = { ...cb, text: '' }
} else {
contentBlocks[idx] = { ...cb }
}
break
}
case 'content_block_delta': {
const idx = (event as any).index
const delta = (event as any).delta
const block = contentBlocks[idx]
if (!block) break
if (delta.type === 'text_delta') {
block.text = (block.text || '') + delta.text
} else if (delta.type === 'input_json_delta') {
block.input = (block.input || '') + delta.partial_json
}
break
}
case 'content_block_stop': {
const idx = (event as any).index
const block = contentBlocks[idx]
if (!block || !partialMessage) break

const m: AssistantMessage = {
message: {
...partialMessage,
content: normalizeContentFromAPI([block], tools, options.agentId),
},
requestId: undefined,
type: 'assistant',
uuid: randomUUID(),
timestamp: new Date().toISOString(),
}
collectedMessages.push(m)
yield m
break
}
case 'message_delta': {
const deltaUsage = (event as any).usage
if (deltaUsage) {
usage = { ...usage, ...deltaUsage }
}
break
}
case 'message_stop':
break
}

if (event.type === 'message_stop' && usage.input_tokens + usage.output_tokens > 0) {
const costUSD = calculateUSDCost(options.model, usage as any)
addToTotalSessionCost(costUSD, usage as any, options.model)
}
Comment on lines +182 to +185
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Inspect calculateUSDCost for unknown-model handling and see if MiniMax pricing exists.
ast-grep --pattern $'export function calculateUSDCost($_, $_) { $$$ }'
rg -nP --type=ts -C3 '\bMiniMax-M2\.7\b|minimax' src/utils/modelCost.ts src/cost-tracker.ts

Repository: claude-code-best/claude-code

Length of output: 54


🏁 Script executed:

# Find where calculateUSDCost is defined
rg -l "calculateUSDCost" --type=ts

Repository: claude-code-best/claude-code

Length of output: 335


🏁 Script executed:

# Search for cost-related files
fd -e ts -e tsx | grep -i cost

Repository: claude-code-best/claude-code

Length of output: 450


🏁 Script executed:

# Search for MiniMax references in the codebase
rg -n "MiniMax|minimax" --type=ts

Repository: claude-code-best/claude-code

Length of output: 7894


🏁 Script executed:

# Read the main modelCost.ts file to see calculateUSDCost implementation
cat -n src/utils/modelCost.ts

Repository: claude-code-best/claude-code

Length of output: 9208


🏁 Script executed:

# Check if there's a cost table with MiniMax pricing
rg -n "MiniMax|calculateUSDCost" src/utils/modelCost.ts -A 5 -B 5

Repository: claude-code-best/claude-code

Length of output: 932


🏁 Script executed:

# Check getDefaultMainLoopModelSetting to understand the fallback
rg -n "getDefaultMainLoopModelSetting" src/utils/model/model.ts -A 10

Repository: claude-code-best/claude-code

Length of output: 1598


🏁 Script executed:

# Check if MiniMax is imported into modelCost.ts or if there's a config for it
rg -n "MINIMAX|minimax" src/utils/modelCost.ts

Repository: claude-code-best/claude-code

Length of output: 54


🏁 Script executed:

# Look at the actual call site in minimax/index.ts to confirm the context
sed -n '180,190p' src/services/api/minimax/index.ts

Repository: claude-code-best/claude-code

Length of output: 461


MiniMax models use fallback pricing tier instead of dedicated cost entries.

MiniMax-M2.7 and MiniMax-M2.7-highspeed are not in the MODEL_COSTS table. When calculateUSDCost is called with an unknown model, it logs an analytics event and returns the default fallback tier (COST_TIER_5_25: $5/$25 per Mtok) rather than throwing an error or breaking the cost flow. However, this means MiniMax usage is tracked at Claude Opus 4.5 rates instead of actual MiniMax pricing. Add MiniMax pricing entries to MODEL_COSTS in src/utils/modelCost.ts to ensure accurate cost tracking.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/services/api/minimax/index.ts` around lines 182 - 185, The MODEL_COSTS
lookup is missing entries for "MiniMax-M2.7" and "MiniMax-M2.7-highspeed",
causing calculateUSDCost to fall back to COST_TIER_5_25; update the MODEL_COSTS
object to include these two model keys with the correct pricing objects/tier
entries matching the existing model price shape so calculateUSDCost will return
accurate costs instead of the fallback. Ensure the entries use the same
structure as other models in MODEL_COSTS so add keys "MiniMax-M2.7" and
"MiniMax-M2.7-highspeed" with their corresponding pricing info.


yield {
type: 'stream_event',
event: event as any,
...(event.type === 'message_start' ? { ttftMs } : undefined),
} as StreamEvent
}
Comment on lines +66 to +192
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🌐 Web query:

Anthropic SDK TypeScript BetaRawMessageStreamEvent discriminated union types

💡 Result:

Yes, BetaRawMessageStreamEvent in the Anthropic TypeScript SDK is a discriminated union type based on the type field. It unions several specific event interfaces for handling streaming responses from the beta Messages API. The type is defined as: export type BetaRawMessageStreamEvent = | BetaRawMessageStartEvent | BetaRawMessageDeltaEvent | BetaRawMessageStopEvent | BetaRawContentBlockStartEvent | BetaRawContentBlockDeltaEvent | BetaRawContentBlockStopEvent; Each variant has a literal type discriminator: - BetaRawMessageStartEvent: { type: 'message_start'; message: BetaMessage } - BetaRawMessageStopEvent: { type: 'message_stop' } - BetaRawMessageDeltaEvent: { type: 'message_delta'; delta: { ... } } - BetaRawContentBlockStartEvent: { type: 'content_block_start'; content_block: BetaContentBlock } - BetaRawContentBlockDeltaEvent: { type: 'content_block_delta'; index: number; delta: BetaRawContentBlockDelta } - BetaRawContentBlockStopEvent: { type: 'content_block_stop'; index: number } This union is imported and aliased as BetaMessageStreamEvent in BetaMessageStream, used in event handlers like streamEvent: (event: BetaMessageStreamEvent, snapshot: BetaMessage) => void. It enables type-safe pattern matching/switching on event.type in streaming code, as shown in SDK examples and source. The SDK uses this for beta features (e.g., client.beta.messages.stream), supporting advanced streaming like tool use, thinking blocks, and content deltas.

Citations:


🏁 Script executed:

#!/bin/bash

# Check imports and client type in minimax file
head -30 src/services/api/minimax/index.ts

# Also check what getMiniMaxClient is
rg "getMiniMaxClient" -A 5 src/services/api/minimax/

Repository: claude-code-best/claude-code

Length of output: 6063


🏁 Script executed:

#!/bin/bash

# Check full client.ts implementation
cat src/services/api/minimax/client.ts

# Check claude.ts streaming pattern (mentioned as reference)
rg "for await.*stream" src/services/api/claude.ts -A 80 | head -150

Repository: claude-code-best/claude-code

Length of output: 4620


🏁 Script executed:

#!/bin/bash

# Check what types are imported in minimax and if BetaRawMessageStreamEvent exists
rg "import.*BetaRaw|import.*stream|MessageStreamEvent" src/services/api/minimax/

# Check full streaming event handling in minimax (lines 100-150)
sed -n '100,150p' src/services/api/minimax/index.ts

# Also check what type annotations exist
rg "contentBlocks|partialMessage|const.*=" src/services/api/minimax/index.ts -B 1 -A 1 | head -60

Repository: claude-code-best/claude-code

Length of output: 3601


🏁 Script executed:

#!/bin/bash

# Check all imports in minimax/index.ts
head -25 src/services/api/minimax/index.ts

# Check what types are available from Anthropic SDK
rg "BetaRawMessage|BetaMessage|BetaContent" src/services/api/minimax/ src/services/api/claude.ts

# Check claude.ts imports to see what types it uses
head -40 src/services/api/claude.ts | grep -E "^import"

Repository: claude-code-best/claude-code

Length of output: 4915


Import and use proper Anthropic types instead of any — violates strict-typing guideline.

The streaming loop uses any extensively (Record<number, any>, let partialMessage: any, (event as any).message, (event as any).index, usage as any). This defeats the Anthropic SDK's discriminated union types for stream events.

client.messages.stream() returns Stream<BetaRawMessageStreamEvent>, a discriminated union where each event type has specific fields. The src/services/api/claude.ts file in this same codebase demonstrates the correct pattern — it imports BetaRawMessageStreamEvent, BetaContentBlock, and BetaMessage and uses them directly without casts.

Add the missing imports and replace the type annotations:

Required imports and fixes
  import type { BetaToolUnion } from '@anthropic-ai/sdk/resources/beta/messages/messages.mjs'
+ import type { BetaRawMessageStreamEvent, BetaContentBlock, BetaMessage } from '@anthropic-ai/sdk/resources/beta/messages/messages.mjs'
-    const contentBlocks: Record<number, any> = {}
+    const contentBlocks: Record<number, BetaContentBlock> = {}
     const collectedMessages: AssistantMessage[] = []
-    let partialMessage: any = undefined
+    let partialMessage: BetaMessage | undefined = undefined

Then narrow event fields via the discriminated union in each case branch instead of (event as any).<field>.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/services/api/minimax/index.ts` around lines 66 - 192, The stream handling
uses untyped any casts; import and use the Anthropic SDK types (e.g.
BetaRawMessageStreamEvent, BetaContentBlock, BetaMessage) and narrow the event
union instead of casting. Specifically: add imports for
BetaRawMessageStreamEvent/BetaContentBlock/BetaMessage, type the stream result
as AsyncIterable<BetaRawMessageStreamEvent>, declare contentBlocks as
Record<number, BetaContentBlock>, declare partialMessage as BetaMessage |
undefined, and replace occurrences like (event as any).message, .index, .delta,
and usage as any with the properly typed properties from the discriminated union
inside each case branch (message_start -> event.message, content_block_* ->
event.index/event.content_block, content_block_delta -> event.delta,
message_delta/usage -> event.usage). Ensure
calculateUSDCost/addToTotalSessionCost calls use the properly typed usage
object.


// Record LLM observation in Langfuse (no-op if not configured)
recordLLMObservation(options.langfuseTrace ?? null, {
model: options.model,
provider: 'minimax',
input: convertMessagesToLangfuse(messagesForAPI, systemPrompt),
output: convertOutputToLangfuse(collectedMessages),
usage: {
input_tokens: usage.input_tokens,
output_tokens: usage.output_tokens,
cache_creation_input_tokens: usage.cache_creation_input_tokens,
cache_read_input_tokens: usage.cache_read_input_tokens,
},
startTime: new Date(start),
endTime: new Date(),
completionStartTime: ttftMs > 0 ? new Date(start + ttftMs) : undefined,
tools: convertToolsToLangfuse(toolSchemas as unknown[]),
})
} catch (error) {
const errorMessage = error instanceof Error ? error.message : String(error)
logForDebugging(`[MiniMax] Error: ${errorMessage}`, { level: 'error' })
yield createAssistantAPIErrorMessage({
content: `API Error: ${errorMessage}`,
apiError: 'api_error',
error: (error instanceof Error ? error : new Error(String(error))) as unknown as SDKAssistantMessageError,
})
}
}
Loading