@llumiverse/drivers 1.2.0 → 1.4.0-dev.20260629.090753Z
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/{types/adobe → adobe}/firefly.d.ts +2 -1
- package/lib/adobe/firefly.d.ts.map +1 -0
- package/lib/adobe/firefly.js +120 -0
- package/lib/adobe/firefly.js.map +1 -0
- package/lib/anthropic/index.d.ts +23 -0
- package/lib/anthropic/index.d.ts.map +1 -0
- package/lib/anthropic/index.js +68 -0
- package/lib/anthropic/index.js.map +1 -0
- package/lib/{types/azure → azure}/azure_foundry.d.ts +9 -7
- package/lib/azure/azure_foundry.d.ts.map +1 -0
- package/lib/{esm/azure → azure}/azure_foundry.js +138 -124
- package/lib/azure/azure_foundry.js.map +1 -0
- package/lib/{types/bedrock → bedrock}/converse.d.ts +2 -2
- package/lib/bedrock/converse.d.ts.map +1 -0
- package/lib/{esm/bedrock → bedrock}/converse.js +86 -79
- package/lib/bedrock/converse.js.map +1 -0
- package/lib/bedrock/embeddings.d.ts +4 -0
- package/lib/bedrock/embeddings.d.ts.map +1 -0
- package/lib/bedrock/embeddings.js +359 -0
- package/lib/bedrock/embeddings.js.map +1 -0
- package/lib/{types/bedrock → bedrock}/index.d.ts +33 -9
- package/lib/bedrock/index.d.ts.map +1 -0
- package/lib/{esm/bedrock → bedrock}/index.js +599 -324
- package/lib/bedrock/index.js.map +1 -0
- package/lib/{types/bedrock → bedrock}/nova-image-payload.d.ts +5 -5
- package/lib/bedrock/nova-image-payload.d.ts.map +1 -0
- package/lib/{esm/bedrock → bedrock}/nova-image-payload.js +31 -36
- package/lib/bedrock/nova-image-payload.js.map +1 -0
- package/lib/{types/bedrock → bedrock}/payloads.d.ts +1 -1
- package/lib/bedrock/payloads.d.ts.map +1 -0
- package/lib/{cjs/bedrock → bedrock}/payloads.js.map +1 -1
- package/lib/{types/bedrock → bedrock}/s3.d.ts +3 -2
- package/lib/bedrock/s3.d.ts.map +1 -0
- package/lib/{esm/bedrock → bedrock}/s3.js +27 -23
- package/lib/bedrock/s3.js.map +1 -0
- package/lib/{types/bedrock → bedrock}/twelvelabs.d.ts +8 -9
- package/lib/bedrock/twelvelabs.d.ts.map +1 -0
- package/lib/{esm/bedrock → bedrock}/twelvelabs.js +17 -21
- package/lib/bedrock/twelvelabs.js.map +1 -0
- package/lib/{types/groq → groq}/index.d.ts +6 -4
- package/lib/groq/index.d.ts.map +1 -0
- package/lib/{esm/groq → groq}/index.js +72 -39
- package/lib/groq/index.js.map +1 -0
- package/lib/{types/huggingface_ie.d.ts → huggingface_ie.d.ts} +4 -3
- package/lib/huggingface_ie.d.ts.map +1 -0
- package/lib/{esm/huggingface_ie.js → huggingface_ie.js} +32 -31
- package/lib/huggingface_ie.js.map +1 -0
- package/lib/index.d.ts +16 -0
- package/lib/index.d.ts.map +1 -0
- package/lib/index.js +16 -0
- package/lib/index.js.map +1 -0
- package/lib/{types/mistral → mistral}/index.d.ts +6 -5
- package/lib/mistral/index.d.ts.map +1 -0
- package/lib/{esm/mistral → mistral}/index.js +66 -38
- package/lib/mistral/index.js.map +1 -0
- package/lib/{types/mistral → mistral}/types.d.ts +1 -0
- package/lib/mistral/types.d.ts.map +1 -0
- package/lib/mistral/types.js.map +1 -0
- package/lib/{types/openai → openai}/azure_openai.d.ts +5 -4
- package/lib/openai/azure_openai.d.ts.map +1 -0
- package/lib/{esm/openai → openai}/azure_openai.js +17 -14
- package/lib/openai/azure_openai.js.map +1 -0
- package/lib/openai/drivers.d.ts +4 -0
- package/lib/openai/drivers.d.ts.map +1 -0
- package/lib/openai/drivers.js +9 -0
- package/lib/openai/drivers.js.map +1 -0
- package/lib/{types/openai → openai}/index.d.ts +17 -5
- package/lib/openai/index.d.ts.map +1 -0
- package/lib/{esm/openai → openai}/index.js +254 -148
- package/lib/openai/index.js.map +1 -0
- package/lib/{types/openai → openai}/openai.d.ts +3 -3
- package/lib/openai/openai.d.ts.map +1 -0
- package/lib/openai/openai.js +15 -0
- package/lib/openai/openai.js.map +1 -0
- package/lib/{types/openai → openai}/openai_compatible.d.ts +3 -3
- package/lib/openai/openai_compatible.d.ts.map +1 -0
- package/lib/{esm/openai → openai}/openai_compatible.js +14 -11
- package/lib/openai/openai_compatible.js.map +1 -0
- package/lib/{types/openai → openai}/openai_format.d.ts +3 -2
- package/lib/openai/openai_format.d.ts.map +1 -0
- package/lib/{esm/openai → openai}/openai_format.js +66 -24
- package/lib/openai/openai_format.js.map +1 -0
- package/lib/{types/replicate.d.ts → replicate.d.ts} +3 -2
- package/lib/replicate.d.ts.map +1 -0
- package/lib/{esm/replicate.js → replicate.js} +59 -49
- package/lib/replicate.js.map +1 -0
- package/lib/shared/claude-messages.d.ts +99 -0
- package/lib/shared/claude-messages.d.ts.map +1 -0
- package/lib/shared/claude-messages.js +869 -0
- package/lib/shared/claude-messages.js.map +1 -0
- package/lib/{types/shared → shared}/claude-thinking.d.ts +1 -1
- package/lib/shared/claude-thinking.d.ts.map +1 -0
- package/lib/{esm/shared → shared}/claude-thinking.js +5 -7
- package/lib/shared/claude-thinking.js.map +1 -0
- package/lib/shared/debug-prompt.d.ts +5 -0
- package/lib/shared/debug-prompt.d.ts.map +1 -0
- package/lib/shared/debug-prompt.js +25 -0
- package/lib/shared/debug-prompt.js.map +1 -0
- package/lib/{types/test-driver → test-driver}/TestErrorCompletionStream.d.ts +1 -1
- package/lib/test-driver/TestErrorCompletionStream.d.ts.map +1 -0
- package/lib/test-driver/TestErrorCompletionStream.js +16 -0
- package/lib/test-driver/TestErrorCompletionStream.js.map +1 -0
- package/lib/{types/test-driver → test-driver}/TestValidationErrorCompletionStream.d.ts +1 -1
- package/lib/test-driver/TestValidationErrorCompletionStream.d.ts.map +1 -0
- package/lib/{esm/test-driver → test-driver}/TestValidationErrorCompletionStream.js +4 -4
- package/lib/test-driver/TestValidationErrorCompletionStream.js.map +1 -0
- package/lib/{types/test-driver → test-driver}/index.d.ts +3 -3
- package/lib/test-driver/index.d.ts.map +1 -0
- package/lib/{esm/test-driver → test-driver}/index.js +20 -20
- package/lib/test-driver/index.js.map +1 -0
- package/lib/{types/test-driver → test-driver}/utils.d.ts +1 -1
- package/lib/test-driver/utils.d.ts.map +1 -0
- package/lib/{esm/test-driver → test-driver}/utils.js +4 -4
- package/lib/test-driver/utils.js.map +1 -0
- package/lib/togetherai/index.d.ts +15 -0
- package/lib/togetherai/index.d.ts.map +1 -0
- package/lib/togetherai/index.js +54 -0
- package/lib/togetherai/index.js.map +1 -0
- package/lib/{types/togetherai → togetherai}/interfaces.d.ts +1 -1
- package/lib/togetherai/interfaces.d.ts.map +1 -0
- package/lib/{cjs/watsonx → togetherai}/interfaces.js.map +1 -1
- package/lib/vertexai/debug.d.ts +2 -0
- package/lib/vertexai/debug.d.ts.map +1 -0
- package/lib/{esm/vertexai → vertexai}/debug.js +1 -1
- package/lib/vertexai/debug.js.map +1 -0
- package/lib/vertexai/embeddings/embed-legacy-multimodal.d.ts +9 -0
- package/lib/vertexai/embeddings/embed-legacy-multimodal.d.ts.map +1 -0
- package/lib/vertexai/embeddings/embed-legacy-multimodal.js +141 -0
- package/lib/vertexai/embeddings/embed-legacy-multimodal.js.map +1 -0
- package/lib/vertexai/embeddings/embed.d.ts +13 -0
- package/lib/vertexai/embeddings/embed.d.ts.map +1 -0
- package/lib/vertexai/embeddings/embed.js +162 -0
- package/lib/vertexai/embeddings/embed.js.map +1 -0
- package/lib/vertexai/embeddings/source-utils.d.ts +9 -0
- package/lib/vertexai/embeddings/source-utils.d.ts.map +1 -0
- package/lib/vertexai/embeddings/source-utils.js +18 -0
- package/lib/vertexai/embeddings/source-utils.js.map +1 -0
- package/lib/{types/vertexai → vertexai}/index.d.ts +31 -19
- package/lib/vertexai/index.d.ts.map +1 -0
- package/lib/{esm/vertexai → vertexai}/index.js +221 -159
- package/lib/vertexai/index.js.map +1 -0
- package/lib/vertexai/models/claude.d.ts +16 -0
- package/lib/vertexai/models/claude.d.ts.map +1 -0
- package/lib/vertexai/models/claude.js +65 -0
- package/lib/vertexai/models/claude.js.map +1 -0
- package/lib/{types/vertexai → vertexai}/models/gemini.d.ts +29 -5
- package/lib/vertexai/models/gemini.d.ts.map +1 -0
- package/lib/{esm/vertexai → vertexai}/models/gemini.js +255 -127
- package/lib/vertexai/models/gemini.js.map +1 -0
- package/lib/{types/vertexai → vertexai}/models/imagen.d.ts +12 -11
- package/lib/vertexai/models/imagen.d.ts.map +1 -0
- package/lib/{esm/vertexai → vertexai}/models/imagen.js +74 -47
- package/lib/vertexai/models/imagen.js.map +1 -0
- package/lib/{types/vertexai → vertexai}/models/llama.d.ts +4 -4
- package/lib/vertexai/models/llama.d.ts.map +1 -0
- package/lib/{esm/vertexai → vertexai}/models/llama.js +37 -33
- package/lib/vertexai/models/llama.js.map +1 -0
- package/lib/vertexai/models.d.ts +21 -0
- package/lib/vertexai/models.d.ts.map +1 -0
- package/lib/{esm/vertexai → vertexai}/models.js +12 -9
- package/lib/vertexai/models.js.map +1 -0
- package/lib/{types/watsonx → watsonx}/index.d.ts +4 -3
- package/lib/watsonx/index.d.ts.map +1 -0
- package/lib/{esm/watsonx → watsonx}/index.js +69 -41
- package/lib/watsonx/index.js.map +1 -0
- package/lib/watsonx/interfaces.d.ts.map +1 -0
- package/lib/{esm/watsonx → watsonx}/interfaces.js.map +1 -1
- package/lib/{types/xai → xai}/index.d.ts +4 -4
- package/lib/xai/index.d.ts.map +1 -0
- package/lib/{esm/xai → xai}/index.js +22 -18
- package/lib/xai/index.js.map +1 -0
- package/package.json +88 -36
- package/src/adobe/firefly.ts +60 -43
- package/src/anthropic/index.ts +121 -0
- package/src/azure/azure_foundry.ts +210 -165
- package/src/bedrock/converse.ts +124 -101
- package/src/bedrock/embeddings.test.ts +508 -0
- package/src/bedrock/embeddings.ts +517 -0
- package/src/bedrock/error-handling.test.ts +62 -14
- package/src/bedrock/index.ts +832 -466
- package/src/bedrock/nova-image-payload.ts +98 -91
- package/src/bedrock/payloads.ts +9 -9
- package/src/bedrock/s3.test.ts +39 -0
- package/src/bedrock/s3.ts +57 -38
- package/src/bedrock/streaming-tool-use.test.ts +86 -69
- package/src/bedrock/twelvelabs.ts +35 -32
- package/src/groq/index.ts +143 -60
- package/src/http-timeout-wiring.test.ts +263 -0
- package/src/huggingface_ie.ts +45 -61
- package/src/index.ts +15 -15
- package/src/mistral/index.ts +97 -48
- package/src/mistral/types.ts +13 -12
- package/src/openai/azure_openai.ts +35 -27
- package/src/openai/drivers.ts +9 -0
- package/src/openai/error-handling.test.ts +308 -113
- package/src/openai/index.ts +389 -254
- package/src/openai/openai.ts +6 -13
- package/src/openai/openai_compatible.ts +35 -25
- package/src/openai/openai_format.ts +90 -40
- package/src/replicate.ts +108 -86
- package/src/shared/claude-messages.test.ts +37 -0
- package/src/shared/claude-messages.ts +1085 -0
- package/src/shared/claude-thinking.ts +6 -12
- package/src/shared/debug-prompt.ts +28 -0
- package/src/test-driver/TestErrorCompletionStream.ts +9 -10
- package/src/test-driver/TestValidationErrorCompletionStream.ts +9 -10
- package/src/test-driver/index.ts +43 -27
- package/src/test-driver/utils.ts +8 -8
- package/src/togetherai/index.test.ts +32 -0
- package/src/togetherai/index.ts +64 -128
- package/src/togetherai/interfaces.ts +5 -5
- package/src/vertexai/debug.ts +3 -3
- package/src/vertexai/embeddings/embed-legacy-multimodal.ts +211 -0
- package/src/vertexai/embeddings/embed.ts +206 -0
- package/src/vertexai/embeddings/embeddings.test.ts +497 -0
- package/src/vertexai/embeddings/source-utils.test.ts +54 -0
- package/src/vertexai/embeddings/source-utils.ts +29 -0
- package/src/vertexai/index.ts +339 -253
- package/src/vertexai/models/claude-error-handling.test.ts +86 -39
- package/src/vertexai/models/claude-streaming-spacing.test.ts +82 -75
- package/src/vertexai/models/claude.ts +84 -1041
- package/src/vertexai/models/gemini-conversation-mutation.test.ts +65 -23
- package/src/vertexai/models/gemini-error-handling.test.ts +126 -10
- package/src/vertexai/models/gemini-orphaned-tool-result.test.ts +118 -0
- package/src/vertexai/models/gemini.ts +372 -182
- package/src/vertexai/models/imagen.ts +164 -100
- package/src/vertexai/models/llama.ts +70 -48
- package/src/vertexai/models.ts +36 -16
- package/src/watsonx/index.ts +101 -65
- package/src/watsonx/interfaces.ts +10 -15
- package/src/xai/index.ts +30 -30
- package/lib/cjs/adobe/firefly.js +0 -120
- package/lib/cjs/adobe/firefly.js.map +0 -1
- package/lib/cjs/azure/azure_foundry.js +0 -432
- package/lib/cjs/azure/azure_foundry.js.map +0 -1
- package/lib/cjs/bedrock/converse.js +0 -359
- package/lib/cjs/bedrock/converse.js.map +0 -1
- package/lib/cjs/bedrock/index.js +0 -1508
- package/lib/cjs/bedrock/index.js.map +0 -1
- package/lib/cjs/bedrock/nova-image-payload.js +0 -207
- package/lib/cjs/bedrock/nova-image-payload.js.map +0 -1
- package/lib/cjs/bedrock/payloads.js +0 -3
- package/lib/cjs/bedrock/s3.js +0 -107
- package/lib/cjs/bedrock/s3.js.map +0 -1
- package/lib/cjs/bedrock/twelvelabs.js +0 -87
- package/lib/cjs/bedrock/twelvelabs.js.map +0 -1
- package/lib/cjs/groq/index.js +0 -328
- package/lib/cjs/groq/index.js.map +0 -1
- package/lib/cjs/huggingface_ie.js +0 -201
- package/lib/cjs/huggingface_ie.js.map +0 -1
- package/lib/cjs/index.js +0 -31
- package/lib/cjs/index.js.map +0 -1
- package/lib/cjs/mistral/index.js +0 -176
- package/lib/cjs/mistral/index.js.map +0 -1
- package/lib/cjs/mistral/types.js +0 -83
- package/lib/cjs/mistral/types.js.map +0 -1
- package/lib/cjs/openai/azure_openai.js +0 -72
- package/lib/cjs/openai/azure_openai.js.map +0 -1
- package/lib/cjs/openai/index.js +0 -1129
- package/lib/cjs/openai/index.js.map +0 -1
- package/lib/cjs/openai/openai.js +0 -21
- package/lib/cjs/openai/openai.js.map +0 -1
- package/lib/cjs/openai/openai_compatible.js +0 -63
- package/lib/cjs/openai/openai_compatible.js.map +0 -1
- package/lib/cjs/openai/openai_format.js +0 -131
- package/lib/cjs/openai/openai_format.js.map +0 -1
- package/lib/cjs/package.json +0 -3
- package/lib/cjs/replicate.js +0 -275
- package/lib/cjs/replicate.js.map +0 -1
- package/lib/cjs/shared/claude-thinking.js +0 -60
- package/lib/cjs/shared/claude-thinking.js.map +0 -1
- package/lib/cjs/test-driver/TestErrorCompletionStream.js +0 -20
- package/lib/cjs/test-driver/TestErrorCompletionStream.js.map +0 -1
- package/lib/cjs/test-driver/TestValidationErrorCompletionStream.js +0 -24
- package/lib/cjs/test-driver/TestValidationErrorCompletionStream.js.map +0 -1
- package/lib/cjs/test-driver/index.js +0 -109
- package/lib/cjs/test-driver/index.js.map +0 -1
- package/lib/cjs/test-driver/utils.js +0 -30
- package/lib/cjs/test-driver/utils.js.map +0 -1
- package/lib/cjs/togetherai/index.js +0 -126
- package/lib/cjs/togetherai/index.js.map +0 -1
- package/lib/cjs/togetherai/interfaces.js +0 -3
- package/lib/cjs/togetherai/interfaces.js.map +0 -1
- package/lib/cjs/vertexai/debug.js +0 -12
- package/lib/cjs/vertexai/debug.js.map +0 -1
- package/lib/cjs/vertexai/embeddings/embeddings-image.js +0 -27
- package/lib/cjs/vertexai/embeddings/embeddings-image.js.map +0 -1
- package/lib/cjs/vertexai/embeddings/embeddings-text.js +0 -23
- package/lib/cjs/vertexai/embeddings/embeddings-text.js.map +0 -1
- package/lib/cjs/vertexai/index.js +0 -644
- package/lib/cjs/vertexai/index.js.map +0 -1
- package/lib/cjs/vertexai/models/claude.js +0 -917
- package/lib/cjs/vertexai/models/claude.js.map +0 -1
- package/lib/cjs/vertexai/models/gemini.js +0 -858
- package/lib/cjs/vertexai/models/gemini.js.map +0 -1
- package/lib/cjs/vertexai/models/imagen.js +0 -303
- package/lib/cjs/vertexai/models/imagen.js.map +0 -1
- package/lib/cjs/vertexai/models/llama.js +0 -183
- package/lib/cjs/vertexai/models/llama.js.map +0 -1
- package/lib/cjs/vertexai/models.js +0 -35
- package/lib/cjs/vertexai/models.js.map +0 -1
- package/lib/cjs/watsonx/index.js +0 -161
- package/lib/cjs/watsonx/index.js.map +0 -1
- package/lib/cjs/watsonx/interfaces.js +0 -3
- package/lib/cjs/xai/index.js +0 -65
- package/lib/cjs/xai/index.js.map +0 -1
- package/lib/esm/adobe/firefly.js +0 -116
- package/lib/esm/adobe/firefly.js.map +0 -1
- package/lib/esm/azure/azure_foundry.js.map +0 -1
- package/lib/esm/bedrock/converse.js.map +0 -1
- package/lib/esm/bedrock/index.js.map +0 -1
- package/lib/esm/bedrock/nova-image-payload.js.map +0 -1
- package/lib/esm/bedrock/payloads.js.map +0 -1
- package/lib/esm/bedrock/s3.js.map +0 -1
- package/lib/esm/bedrock/twelvelabs.js.map +0 -1
- package/lib/esm/groq/index.js.map +0 -1
- package/lib/esm/huggingface_ie.js.map +0 -1
- package/lib/esm/index.js +0 -15
- package/lib/esm/index.js.map +0 -1
- package/lib/esm/mistral/index.js.map +0 -1
- package/lib/esm/mistral/types.js.map +0 -1
- package/lib/esm/openai/azure_openai.js.map +0 -1
- package/lib/esm/openai/index.js.map +0 -1
- package/lib/esm/openai/openai.js +0 -14
- package/lib/esm/openai/openai.js.map +0 -1
- package/lib/esm/openai/openai_compatible.js.map +0 -1
- package/lib/esm/openai/openai_format.js.map +0 -1
- package/lib/esm/replicate.js.map +0 -1
- package/lib/esm/shared/claude-thinking.js.map +0 -1
- package/lib/esm/test-driver/TestErrorCompletionStream.js +0 -16
- package/lib/esm/test-driver/TestErrorCompletionStream.js.map +0 -1
- package/lib/esm/test-driver/TestValidationErrorCompletionStream.js.map +0 -1
- package/lib/esm/test-driver/index.js.map +0 -1
- package/lib/esm/test-driver/utils.js.map +0 -1
- package/lib/esm/togetherai/index.js +0 -122
- package/lib/esm/togetherai/index.js.map +0 -1
- package/lib/esm/togetherai/interfaces.js.map +0 -1
- package/lib/esm/vertexai/debug.js.map +0 -1
- package/lib/esm/vertexai/embeddings/embeddings-image.js +0 -24
- package/lib/esm/vertexai/embeddings/embeddings-image.js.map +0 -1
- package/lib/esm/vertexai/embeddings/embeddings-text.js +0 -20
- package/lib/esm/vertexai/embeddings/embeddings-text.js.map +0 -1
- package/lib/esm/vertexai/index.js.map +0 -1
- package/lib/esm/vertexai/models/claude.js +0 -906
- package/lib/esm/vertexai/models/claude.js.map +0 -1
- package/lib/esm/vertexai/models/gemini.js.map +0 -1
- package/lib/esm/vertexai/models/imagen.js.map +0 -1
- package/lib/esm/vertexai/models/llama.js.map +0 -1
- package/lib/esm/vertexai/models.js.map +0 -1
- package/lib/esm/watsonx/index.js.map +0 -1
- package/lib/esm/xai/index.js.map +0 -1
- package/lib/types/adobe/firefly.d.ts.map +0 -1
- package/lib/types/azure/azure_foundry.d.ts.map +0 -1
- package/lib/types/bedrock/converse.d.ts.map +0 -1
- package/lib/types/bedrock/index.d.ts.map +0 -1
- package/lib/types/bedrock/nova-image-payload.d.ts.map +0 -1
- package/lib/types/bedrock/payloads.d.ts.map +0 -1
- package/lib/types/bedrock/s3.d.ts.map +0 -1
- package/lib/types/bedrock/twelvelabs.d.ts.map +0 -1
- package/lib/types/groq/index.d.ts.map +0 -1
- package/lib/types/huggingface_ie.d.ts.map +0 -1
- package/lib/types/index.d.ts +0 -15
- package/lib/types/index.d.ts.map +0 -1
- package/lib/types/mistral/index.d.ts.map +0 -1
- package/lib/types/mistral/types.d.ts.map +0 -1
- package/lib/types/openai/azure_openai.d.ts.map +0 -1
- package/lib/types/openai/index.d.ts.map +0 -1
- package/lib/types/openai/openai.d.ts.map +0 -1
- package/lib/types/openai/openai_compatible.d.ts.map +0 -1
- package/lib/types/openai/openai_format.d.ts.map +0 -1
- package/lib/types/replicate.d.ts.map +0 -1
- package/lib/types/shared/claude-thinking.d.ts.map +0 -1
- package/lib/types/test-driver/TestErrorCompletionStream.d.ts.map +0 -1
- package/lib/types/test-driver/TestValidationErrorCompletionStream.d.ts.map +0 -1
- package/lib/types/test-driver/index.d.ts.map +0 -1
- package/lib/types/test-driver/utils.d.ts.map +0 -1
- package/lib/types/togetherai/index.d.ts +0 -23
- package/lib/types/togetherai/index.d.ts.map +0 -1
- package/lib/types/togetherai/interfaces.d.ts.map +0 -1
- package/lib/types/vertexai/debug.d.ts +0 -2
- package/lib/types/vertexai/debug.d.ts.map +0 -1
- package/lib/types/vertexai/embeddings/embeddings-image.d.ts +0 -11
- package/lib/types/vertexai/embeddings/embeddings-image.d.ts.map +0 -1
- package/lib/types/vertexai/embeddings/embeddings-text.d.ts +0 -10
- package/lib/types/vertexai/embeddings/embeddings-text.d.ts.map +0 -1
- package/lib/types/vertexai/index.d.ts.map +0 -1
- package/lib/types/vertexai/models/claude.d.ts +0 -119
- package/lib/types/vertexai/models/claude.d.ts.map +0 -1
- package/lib/types/vertexai/models/gemini.d.ts.map +0 -1
- package/lib/types/vertexai/models/imagen.d.ts.map +0 -1
- package/lib/types/vertexai/models/llama.d.ts.map +0 -1
- package/lib/types/vertexai/models.d.ts +0 -20
- package/lib/types/vertexai/models.d.ts.map +0 -1
- package/lib/types/watsonx/index.d.ts.map +0 -1
- package/lib/types/watsonx/interfaces.d.ts.map +0 -1
- package/lib/types/xai/index.d.ts.map +0 -1
- package/src/vertexai/embeddings/embeddings-image.ts +0 -52
- package/src/vertexai/embeddings/embeddings-text.ts +0 -54
- /package/lib/{esm/bedrock → bedrock}/payloads.js +0 -0
- /package/lib/{esm/mistral → mistral}/types.js +0 -0
- /package/lib/{esm/togetherai → togetherai}/interfaces.js +0 -0
- /package/lib/{types/watsonx → watsonx}/interfaces.d.ts +0 -0
- /package/lib/{esm/watsonx → watsonx}/interfaces.js +0 -0
|
@@ -0,0 +1,1085 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared utilities for Anthropic SDK-based drivers.
|
|
3
|
+
*
|
|
4
|
+
* Used by both the native AnthropicDriver (drivers/src/anthropic/) and the
|
|
5
|
+
* VertexAI Claude pathway (drivers/src/vertexai/models/claude.ts). Both use
|
|
6
|
+
* the same Anthropic Messages API surface — the only difference is the client
|
|
7
|
+
* (Anthropic vs AnthropicVertex) and how auth is wired up.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import type Anthropic from '@anthropic-ai/sdk';
|
|
11
|
+
import {
|
|
12
|
+
AnthropicError,
|
|
13
|
+
APIConnectionError,
|
|
14
|
+
APIConnectionTimeoutError,
|
|
15
|
+
APIError,
|
|
16
|
+
APIUserAbortError,
|
|
17
|
+
AuthenticationError,
|
|
18
|
+
BadRequestError,
|
|
19
|
+
ConflictError,
|
|
20
|
+
InternalServerError,
|
|
21
|
+
NotFoundError,
|
|
22
|
+
PermissionDeniedError,
|
|
23
|
+
RateLimitError,
|
|
24
|
+
UnprocessableEntityError,
|
|
25
|
+
} from '@anthropic-ai/sdk/error';
|
|
26
|
+
import type {
|
|
27
|
+
ContentBlock,
|
|
28
|
+
ContentBlockParam,
|
|
29
|
+
DocumentBlockParam,
|
|
30
|
+
ImageBlockParam,
|
|
31
|
+
Message,
|
|
32
|
+
MessageParam,
|
|
33
|
+
TextBlockParam,
|
|
34
|
+
ToolResultBlockParam,
|
|
35
|
+
} from '@anthropic-ai/sdk/resources/index.js';
|
|
36
|
+
import type { MessageStreamParams } from '@anthropic-ai/sdk/resources/index.mjs';
|
|
37
|
+
import type { MessageCreateParamsBase, RawMessageStreamEvent } from '@anthropic-ai/sdk/resources/messages.js';
|
|
38
|
+
import type AnthropicVertex from '@anthropic-ai/vertex-sdk';
|
|
39
|
+
import { getClaudeMaxTokensLimit } from '@llumiverse/common';
|
|
40
|
+
import {
|
|
41
|
+
type Completion,
|
|
42
|
+
type CompletionChunkObject,
|
|
43
|
+
type CompletionResult,
|
|
44
|
+
type ExecutionOptions,
|
|
45
|
+
type ExecutionTokenUsage,
|
|
46
|
+
getConversationMeta,
|
|
47
|
+
incrementConversationTurn,
|
|
48
|
+
type JSONObject,
|
|
49
|
+
LlumiverseError,
|
|
50
|
+
type LlumiverseErrorContext,
|
|
51
|
+
PromptRole,
|
|
52
|
+
type PromptSegment,
|
|
53
|
+
readStreamAsBase64,
|
|
54
|
+
readStreamAsString,
|
|
55
|
+
type StatelessExecutionOptions,
|
|
56
|
+
stripBase64ImagesFromConversation,
|
|
57
|
+
stripHeartbeatsFromConversation,
|
|
58
|
+
type ToolUse,
|
|
59
|
+
truncateLargeTextInConversation,
|
|
60
|
+
} from '@llumiverse/core';
|
|
61
|
+
import { asyncMap } from '@llumiverse/core/async';
|
|
62
|
+
import { resolveClaudeThinking } from './claude-thinking.js';
|
|
63
|
+
import { truncateBinaryForDebug } from './debug-prompt.js';
|
|
64
|
+
|
|
65
|
+
// ============================================================================
|
|
66
|
+
// Types
|
|
67
|
+
// ============================================================================
|
|
68
|
+
|
|
69
|
+
// Conversation text-trim policy (applied only when the caller requests text
|
|
70
|
+
// trimming via stripTextMaxTokens). Keep the last N messages fully intact (the
|
|
71
|
+
// active working set) and cap large text in older messages at the token ceiling
|
|
72
|
+
// below — so long agent conversations don't balloon context.
|
|
73
|
+
const KEEP_RECENT_MESSAGES = 12;
|
|
74
|
+
const OLD_MESSAGE_TEXT_MAX_TOKENS = 2000;
|
|
75
|
+
|
|
76
|
+
interface WarnLogger {
|
|
77
|
+
warn: (data: Record<string, unknown>, message: string) => void;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
export interface ClaudePrompt {
|
|
81
|
+
messages: MessageParam[];
|
|
82
|
+
system?: TextBlockParam[];
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function formatClaudeContentBlockForDebug(block: ContentBlockParam): ContentBlockParam {
|
|
86
|
+
if (block.type === 'image' && block.source.type === 'base64') {
|
|
87
|
+
return {
|
|
88
|
+
...block,
|
|
89
|
+
source: {
|
|
90
|
+
...block.source,
|
|
91
|
+
data: truncateBinaryForDebug(block.source.data),
|
|
92
|
+
},
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
if (block.type === 'document' && block.source.type === 'base64') {
|
|
96
|
+
return {
|
|
97
|
+
...block,
|
|
98
|
+
source: {
|
|
99
|
+
...block.source,
|
|
100
|
+
data: truncateBinaryForDebug(block.source.data),
|
|
101
|
+
},
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
return block;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
export function formatClaudeDebugPrompt(prompt: ClaudePrompt): ClaudePrompt {
|
|
108
|
+
return {
|
|
109
|
+
...prompt,
|
|
110
|
+
messages: prompt.messages.map((message) => ({
|
|
111
|
+
...message,
|
|
112
|
+
content: Array.isArray(message.content)
|
|
113
|
+
? message.content.map(formatClaudeContentBlockForDebug)
|
|
114
|
+
: message.content,
|
|
115
|
+
})),
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
export interface AnthropicUsageLike {
|
|
120
|
+
input_tokens: number;
|
|
121
|
+
output_tokens: number;
|
|
122
|
+
cache_read_input_tokens?: number | null;
|
|
123
|
+
cache_creation_input_tokens?: number | null;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* Duck-typed options interface accepted by the shared Claude utilities.
|
|
128
|
+
* Both `AnthropicClaudeOptions` and `VertexAIClaudeOptions` satisfy this structurally.
|
|
129
|
+
*/
|
|
130
|
+
export interface ClaudeBaseOptions {
|
|
131
|
+
_option_id?: string;
|
|
132
|
+
max_tokens?: number;
|
|
133
|
+
temperature?: number;
|
|
134
|
+
top_p?: number;
|
|
135
|
+
top_k?: number;
|
|
136
|
+
stop_sequence?: string[];
|
|
137
|
+
effort?: string;
|
|
138
|
+
thinking_budget_tokens?: number;
|
|
139
|
+
include_thoughts?: boolean;
|
|
140
|
+
cache_enabled?: boolean;
|
|
141
|
+
cache_ttl?: string;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
interface RequestOptions {
|
|
145
|
+
headers?: Record<string, string>;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
type ClaudeTool = NonNullable<MessageCreateParamsBase['tools']>[number];
|
|
149
|
+
type ClaudeMessageStream = AsyncIterable<RawMessageStreamEvent> & {
|
|
150
|
+
finalMessage(): Promise<Message>;
|
|
151
|
+
};
|
|
152
|
+
type ClaudeMessagesStreamClient = {
|
|
153
|
+
messages: {
|
|
154
|
+
stream(body: MessageStreamParams, options?: RequestOptions): ClaudeMessageStream;
|
|
155
|
+
};
|
|
156
|
+
};
|
|
157
|
+
|
|
158
|
+
function streamClaudeMessages(
|
|
159
|
+
client: Anthropic | AnthropicVertex,
|
|
160
|
+
payload: MessageStreamParams,
|
|
161
|
+
requestOptions: RequestOptions | undefined,
|
|
162
|
+
): Promise<ClaudeMessageStream> {
|
|
163
|
+
// AnthropicVertex intentionally wraps the Anthropic Messages API, but it depends on its
|
|
164
|
+
// own @anthropic-ai/sdk copy. Cast at the boundary so the implementation can call the
|
|
165
|
+
// shared runtime-compatible stream API without TS trying to call a union of SDK versions.
|
|
166
|
+
return Promise.resolve((client as unknown as ClaudeMessagesStreamClient).messages.stream(payload, requestOptions));
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
// ============================================================================
|
|
170
|
+
// Token usage
|
|
171
|
+
// ============================================================================
|
|
172
|
+
|
|
173
|
+
export function anthropicUsageToTokenUsage(usage: AnthropicUsageLike): ExecutionTokenUsage {
|
|
174
|
+
const cacheRead = usage.cache_read_input_tokens ?? 0;
|
|
175
|
+
const cacheWrite = usage.cache_creation_input_tokens ?? 0;
|
|
176
|
+
return {
|
|
177
|
+
prompt_new: usage.input_tokens,
|
|
178
|
+
prompt: usage.input_tokens + cacheRead + cacheWrite,
|
|
179
|
+
result: usage.output_tokens,
|
|
180
|
+
total: usage.input_tokens + usage.output_tokens + cacheRead + cacheWrite,
|
|
181
|
+
prompt_cached: usage.cache_read_input_tokens ?? undefined,
|
|
182
|
+
prompt_cache_write: usage.cache_creation_input_tokens ?? undefined,
|
|
183
|
+
};
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
// ============================================================================
|
|
187
|
+
// Finish reason
|
|
188
|
+
// ============================================================================
|
|
189
|
+
|
|
190
|
+
export function claudeFinishReason(reason: string | undefined): string | undefined {
|
|
191
|
+
if (!reason) return undefined;
|
|
192
|
+
switch (reason) {
|
|
193
|
+
case 'end_turn':
|
|
194
|
+
return 'stop';
|
|
195
|
+
case 'max_tokens':
|
|
196
|
+
return 'length';
|
|
197
|
+
default:
|
|
198
|
+
return reason; // stop_sequence, tool_use
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
// ============================================================================
|
|
203
|
+
// Content extraction
|
|
204
|
+
// ============================================================================
|
|
205
|
+
|
|
206
|
+
export function collectClaudeTools(content: ContentBlock[]): ToolUse[] | undefined {
|
|
207
|
+
const out: ToolUse[] = [];
|
|
208
|
+
for (const block of content) {
|
|
209
|
+
if (block.type === 'tool_use') {
|
|
210
|
+
out.push({
|
|
211
|
+
id: block.id,
|
|
212
|
+
tool_name: block.name,
|
|
213
|
+
tool_input: block.input as JSONObject,
|
|
214
|
+
});
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
return out.length > 0 ? out : undefined;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
export function collectAllTextContent(content: ContentBlock[], includeThoughts = false): string {
|
|
221
|
+
const textParts: string[] = [];
|
|
222
|
+
|
|
223
|
+
if (includeThoughts) {
|
|
224
|
+
for (const block of content) {
|
|
225
|
+
if (block.type === 'thinking' && block.thinking) {
|
|
226
|
+
textParts.push(block.thinking);
|
|
227
|
+
} else if (block.type === 'redacted_thinking' && block.data) {
|
|
228
|
+
textParts.push(`[Redacted thinking: ${block.data}]`);
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
if (textParts.length > 0) {
|
|
232
|
+
textParts.push('');
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
for (const block of content) {
|
|
237
|
+
if (block.type === 'text' && block.text) {
|
|
238
|
+
textParts.push(block.text);
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
return textParts.join('\n');
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
// ============================================================================
|
|
246
|
+
// Max tokens
|
|
247
|
+
// ============================================================================
|
|
248
|
+
|
|
249
|
+
export function claudeMaxTokens(option: StatelessExecutionOptions): number {
|
|
250
|
+
const modelOptions = option.model_options as ClaudeBaseOptions | undefined;
|
|
251
|
+
if (modelOptions && typeof modelOptions.max_tokens === 'number') {
|
|
252
|
+
return modelOptions.max_tokens;
|
|
253
|
+
}
|
|
254
|
+
let maxSupportedTokens = getClaudeMaxTokensLimit(option.model);
|
|
255
|
+
// Claude 3.7 supports up to 128k with a beta header; default to 64k when no budget is set.
|
|
256
|
+
if (option.model.includes('claude-3-7-sonnet') && (modelOptions?.thinking_budget_tokens ?? 0) < 48000) {
|
|
257
|
+
maxSupportedTokens = 64000;
|
|
258
|
+
}
|
|
259
|
+
return maxSupportedTokens;
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
// ============================================================================
|
|
263
|
+
// File / multimodal block helpers
|
|
264
|
+
// ============================================================================
|
|
265
|
+
|
|
266
|
+
async function collectFileBlocks(
|
|
267
|
+
segment: PromptSegment,
|
|
268
|
+
logger?: WarnLogger,
|
|
269
|
+
): Promise<Array<TextBlockParam | ImageBlockParam | DocumentBlockParam>> {
|
|
270
|
+
const contentBlocks: Array<TextBlockParam | ImageBlockParam | DocumentBlockParam> = [];
|
|
271
|
+
|
|
272
|
+
for (const file of segment.files || []) {
|
|
273
|
+
if (file.mime_type?.startsWith('image/')) {
|
|
274
|
+
const allowedTypes = ['image/png', 'image/jpeg', 'image/gif', 'image/webp'];
|
|
275
|
+
if (!allowedTypes.includes(file.mime_type)) {
|
|
276
|
+
throw new Error(`Unsupported image type: ${file.mime_type}`);
|
|
277
|
+
}
|
|
278
|
+
const mimeType = String(file.mime_type) as 'image/png' | 'image/jpeg' | 'image/gif' | 'image/webp';
|
|
279
|
+
contentBlocks.push({
|
|
280
|
+
type: 'image',
|
|
281
|
+
source: {
|
|
282
|
+
type: 'base64',
|
|
283
|
+
data: await readStreamAsBase64(await file.getStream()),
|
|
284
|
+
media_type: mimeType,
|
|
285
|
+
},
|
|
286
|
+
} satisfies ImageBlockParam);
|
|
287
|
+
} else if (file.mime_type?.startsWith('video/')) {
|
|
288
|
+
logger?.warn(
|
|
289
|
+
{
|
|
290
|
+
file_name: file.name,
|
|
291
|
+
mime_type: file.mime_type,
|
|
292
|
+
},
|
|
293
|
+
'[Claude] Skipping unsupported video attachment',
|
|
294
|
+
);
|
|
295
|
+
} else if (file.mime_type === 'application/pdf') {
|
|
296
|
+
contentBlocks.push({
|
|
297
|
+
title: file.name,
|
|
298
|
+
type: 'document',
|
|
299
|
+
source: {
|
|
300
|
+
type: 'base64',
|
|
301
|
+
data: await readStreamAsBase64(await file.getStream()),
|
|
302
|
+
media_type: 'application/pdf',
|
|
303
|
+
},
|
|
304
|
+
} satisfies DocumentBlockParam);
|
|
305
|
+
} else if (file.mime_type?.startsWith('text/')) {
|
|
306
|
+
contentBlocks.push({
|
|
307
|
+
title: file.name,
|
|
308
|
+
type: 'document',
|
|
309
|
+
source: {
|
|
310
|
+
type: 'text',
|
|
311
|
+
data: await readStreamAsString(await file.getStream()),
|
|
312
|
+
media_type: 'text/plain',
|
|
313
|
+
},
|
|
314
|
+
} satisfies DocumentBlockParam);
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
return contentBlocks;
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
// ============================================================================
|
|
322
|
+
// Prompt formatting (PromptSegment[] → ClaudePrompt)
|
|
323
|
+
// ============================================================================
|
|
324
|
+
|
|
325
|
+
export async function formatClaudePrompt(
|
|
326
|
+
segments: PromptSegment[],
|
|
327
|
+
options: ExecutionOptions,
|
|
328
|
+
logger?: WarnLogger,
|
|
329
|
+
): Promise<ClaudePrompt> {
|
|
330
|
+
let system: TextBlockParam[] | undefined = segments
|
|
331
|
+
.filter((s) => s.role === PromptRole.system)
|
|
332
|
+
.map((s) => ({ text: s.content, type: 'text' as const }));
|
|
333
|
+
|
|
334
|
+
if (options.result_schema) {
|
|
335
|
+
const schemaText =
|
|
336
|
+
options.tools && options.tools.length > 0
|
|
337
|
+
? `When not calling tools, the answer must be a JSON object using the following JSON Schema:\n${JSON.stringify(options.result_schema)}`
|
|
338
|
+
: `The answer must be a JSON object using the following JSON Schema:\n${JSON.stringify(options.result_schema)}`;
|
|
339
|
+
system.push({ text: schemaText, type: 'text' as const });
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
let messages: MessageParam[] = [];
|
|
343
|
+
const safetyMessages: MessageParam[] = [];
|
|
344
|
+
|
|
345
|
+
for (const segment of segments) {
|
|
346
|
+
if (segment.role === PromptRole.system) continue;
|
|
347
|
+
|
|
348
|
+
if (segment.role === PromptRole.tool) {
|
|
349
|
+
if (!segment.tool_use_id) {
|
|
350
|
+
throw new Error('Tool prompt segment must have a tool use ID');
|
|
351
|
+
}
|
|
352
|
+
const contentBlocks: Array<TextBlockParam | ImageBlockParam | DocumentBlockParam> = [];
|
|
353
|
+
if (segment.content) {
|
|
354
|
+
contentBlocks.push({ type: 'text', text: segment.content } satisfies TextBlockParam);
|
|
355
|
+
}
|
|
356
|
+
contentBlocks.push(...(await collectFileBlocks(segment, logger)));
|
|
357
|
+
messages.push({
|
|
358
|
+
role: 'user',
|
|
359
|
+
content: [
|
|
360
|
+
{
|
|
361
|
+
type: 'tool_result',
|
|
362
|
+
tool_use_id: segment.tool_use_id,
|
|
363
|
+
content: contentBlocks,
|
|
364
|
+
} satisfies ToolResultBlockParam,
|
|
365
|
+
],
|
|
366
|
+
});
|
|
367
|
+
} else {
|
|
368
|
+
const contentBlocks: ContentBlockParam[] = [];
|
|
369
|
+
if (segment.content) {
|
|
370
|
+
contentBlocks.push({ type: 'text', text: segment.content } satisfies TextBlockParam);
|
|
371
|
+
}
|
|
372
|
+
contentBlocks.push(...(await collectFileBlocks(segment, logger)));
|
|
373
|
+
if (contentBlocks.length === 0) continue;
|
|
374
|
+
|
|
375
|
+
const messageParam: MessageParam = {
|
|
376
|
+
role: segment.role === PromptRole.assistant ? 'assistant' : 'user',
|
|
377
|
+
content: contentBlocks,
|
|
378
|
+
};
|
|
379
|
+
|
|
380
|
+
if (segment.role === PromptRole.safety) {
|
|
381
|
+
safetyMessages.push(messageParam);
|
|
382
|
+
} else {
|
|
383
|
+
messages.push(messageParam);
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
messages = messages.concat(safetyMessages);
|
|
389
|
+
if (system && system.length === 0) system = undefined;
|
|
390
|
+
|
|
391
|
+
return { messages, system };
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
// ============================================================================
|
|
395
|
+
// Conversation management
|
|
396
|
+
// ============================================================================
|
|
397
|
+
|
|
398
|
+
export function createPromptFromResponse(response: Message): ClaudePrompt {
|
|
399
|
+
return {
|
|
400
|
+
messages: [{ role: response.role, content: response.content }],
|
|
401
|
+
system: undefined,
|
|
402
|
+
};
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
export function mergeConsecutiveUserMessages(messages: MessageParam[]): MessageParam[] {
|
|
406
|
+
if (messages.length === 0) return [];
|
|
407
|
+
|
|
408
|
+
const needsMerging = messages.some(
|
|
409
|
+
(msg, i) => i < messages.length - 1 && msg.role === 'user' && messages[i + 1].role === 'user',
|
|
410
|
+
);
|
|
411
|
+
if (!needsMerging) return messages;
|
|
412
|
+
|
|
413
|
+
const result: MessageParam[] = [];
|
|
414
|
+
let i = 0;
|
|
415
|
+
while (i < messages.length) {
|
|
416
|
+
const current = messages[i];
|
|
417
|
+
if (current.role === 'user') {
|
|
418
|
+
const mergedContent: MessageParam['content'] = [];
|
|
419
|
+
while (i < messages.length && messages[i].role === 'user') {
|
|
420
|
+
const userMsg = messages[i];
|
|
421
|
+
if (Array.isArray(userMsg.content)) {
|
|
422
|
+
mergedContent.push(...userMsg.content);
|
|
423
|
+
} else if (typeof userMsg.content === 'string') {
|
|
424
|
+
mergedContent.push({ type: 'text', text: userMsg.content });
|
|
425
|
+
}
|
|
426
|
+
i++;
|
|
427
|
+
}
|
|
428
|
+
result.push({ role: 'user', content: mergedContent });
|
|
429
|
+
} else {
|
|
430
|
+
result.push(current);
|
|
431
|
+
i++;
|
|
432
|
+
}
|
|
433
|
+
}
|
|
434
|
+
return result;
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
export function sanitizeMessages(messages: MessageParam[]): MessageParam[] {
|
|
438
|
+
const result: MessageParam[] = [];
|
|
439
|
+
for (const message of messages) {
|
|
440
|
+
if (typeof message.content === 'string') {
|
|
441
|
+
if (message.content.trim()) result.push(message);
|
|
442
|
+
continue;
|
|
443
|
+
}
|
|
444
|
+
const filteredContent = message.content.filter((block) => {
|
|
445
|
+
if (block.type === 'text') return block.text && block.text.trim().length > 0;
|
|
446
|
+
return true;
|
|
447
|
+
});
|
|
448
|
+
if (filteredContent.length > 0) {
|
|
449
|
+
result.push({ ...message, content: filteredContent });
|
|
450
|
+
}
|
|
451
|
+
}
|
|
452
|
+
return result;
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
export function fixOrphanedToolUse(messages: MessageParam[]): MessageParam[] {
|
|
456
|
+
if (messages.length < 2) return messages;
|
|
457
|
+
const result: MessageParam[] = [];
|
|
458
|
+
for (let i = 0; i < messages.length; i++) {
|
|
459
|
+
const current = messages[i];
|
|
460
|
+
result.push(current);
|
|
461
|
+
|
|
462
|
+
if (current.role === 'assistant' && Array.isArray(current.content)) {
|
|
463
|
+
const toolUseBlocks = current.content.filter(
|
|
464
|
+
(block): block is ContentBlockParam & { type: 'tool_use'; id: string; name: string } =>
|
|
465
|
+
block.type === 'tool_use',
|
|
466
|
+
);
|
|
467
|
+
|
|
468
|
+
if (toolUseBlocks.length > 0) {
|
|
469
|
+
const nextMessage = messages[i + 1];
|
|
470
|
+
|
|
471
|
+
if (nextMessage && nextMessage.role === 'user' && Array.isArray(nextMessage.content)) {
|
|
472
|
+
const toolResultIds = new Set(
|
|
473
|
+
nextMessage.content
|
|
474
|
+
.filter((block): block is ToolResultBlockParam => block.type === 'tool_result')
|
|
475
|
+
.map((block) => block.tool_use_id),
|
|
476
|
+
);
|
|
477
|
+
const orphaned = toolUseBlocks.filter((block) => !toolResultIds.has(block.id));
|
|
478
|
+
if (orphaned.length > 0) {
|
|
479
|
+
const syntheticResults: ToolResultBlockParam[] = orphaned.map((block) => ({
|
|
480
|
+
type: 'tool_result',
|
|
481
|
+
tool_use_id: block.id,
|
|
482
|
+
content: `[Tool interrupted: The user stopped the operation before "${block.name}" could execute.]`,
|
|
483
|
+
}));
|
|
484
|
+
messages[i + 1] = { ...nextMessage, content: [...syntheticResults, ...nextMessage.content] };
|
|
485
|
+
}
|
|
486
|
+
} else if (nextMessage && nextMessage.role === 'user') {
|
|
487
|
+
const syntheticResults: ToolResultBlockParam[] = toolUseBlocks.map((block) => ({
|
|
488
|
+
type: 'tool_result',
|
|
489
|
+
tool_use_id: block.id,
|
|
490
|
+
content: `[Tool interrupted: The user stopped the operation before "${block.name}" could execute.]`,
|
|
491
|
+
}));
|
|
492
|
+
const textContent: TextBlockParam =
|
|
493
|
+
typeof nextMessage.content === 'string'
|
|
494
|
+
? { type: 'text', text: nextMessage.content }
|
|
495
|
+
: { type: 'text', text: '' };
|
|
496
|
+
messages[i + 1] = { role: 'user', content: [...syntheticResults, textContent] };
|
|
497
|
+
}
|
|
498
|
+
}
|
|
499
|
+
}
|
|
500
|
+
}
|
|
501
|
+
return result;
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
/**
|
|
505
|
+
* Drop tool_result blocks whose tool_use_id has no matching tool_use in the
|
|
506
|
+
* immediately preceding assistant message. This is the mirror of
|
|
507
|
+
* {@link fixOrphanedToolUse}: that function synthesizes results for tool_uses
|
|
508
|
+
* left unanswered (e.g. a cancelled run); this one removes results left dangling
|
|
509
|
+
* after their tool_use was dropped (e.g. by conversation compaction/trimming, or
|
|
510
|
+
* a parallel tool batch whose results were split across messages that cannot be
|
|
511
|
+
* re-paired).
|
|
512
|
+
*
|
|
513
|
+
* Without this, the Anthropic / Vertex-Anthropic API rejects the request with a
|
|
514
|
+
* non-retryable 400: "unexpected `tool_use_id` found in `tool_result` blocks ...
|
|
515
|
+
* Each `tool_result` block must have a corresponding `tool_use` block in the
|
|
516
|
+
* previous message." — which terminates the conversation.
|
|
517
|
+
*
|
|
518
|
+
* Must run AFTER mergeConsecutiveUserMessages so that parallel tool results which
|
|
519
|
+
* were split across separate user messages are first combined into the single
|
|
520
|
+
* user turn that follows the assistant tool_use message (otherwise valid results
|
|
521
|
+
* whose "previous message" is another user message would be wrongly dropped).
|
|
522
|
+
*/
|
|
523
|
+
export function fixOrphanedToolResults(messages: MessageParam[]): MessageParam[] {
|
|
524
|
+
if (messages.length === 0) return messages;
|
|
525
|
+
const result: MessageParam[] = [];
|
|
526
|
+
for (let i = 0; i < messages.length; i++) {
|
|
527
|
+
const message = messages[i];
|
|
528
|
+
if (message.role !== 'user' || !Array.isArray(message.content)) {
|
|
529
|
+
result.push(message);
|
|
530
|
+
continue;
|
|
531
|
+
}
|
|
532
|
+
const hasToolResult = message.content.some((block) => block.type === 'tool_result');
|
|
533
|
+
if (!hasToolResult) {
|
|
534
|
+
result.push(message);
|
|
535
|
+
continue;
|
|
536
|
+
}
|
|
537
|
+
// Collect the tool_use ids declared by the immediately preceding assistant message.
|
|
538
|
+
const prev = messages[i - 1];
|
|
539
|
+
const allowedIds = new Set<string>();
|
|
540
|
+
if (prev && prev.role === 'assistant' && Array.isArray(prev.content)) {
|
|
541
|
+
for (const block of prev.content) {
|
|
542
|
+
if (block.type === 'tool_use') allowedIds.add(block.id);
|
|
543
|
+
}
|
|
544
|
+
}
|
|
545
|
+
const filtered = message.content.filter((block) =>
|
|
546
|
+
block.type === 'tool_result' ? allowedIds.has(block.tool_use_id) : true,
|
|
547
|
+
);
|
|
548
|
+
// If every block was an orphaned tool_result, drop the message rather than
|
|
549
|
+
// emit an empty (and invalid) content array.
|
|
550
|
+
if (filtered.length === 0) continue;
|
|
551
|
+
result.push(filtered.length === message.content.length ? message : { ...message, content: filtered });
|
|
552
|
+
}
|
|
553
|
+
return result;
|
|
554
|
+
}
|
|
555
|
+
|
|
556
|
+
export function updateClaudeConversation(
|
|
557
|
+
conversation: ClaudePrompt | undefined | null,
|
|
558
|
+
prompt: ClaudePrompt,
|
|
559
|
+
): ClaudePrompt {
|
|
560
|
+
const baseSystemMessages = conversation?.system || [];
|
|
561
|
+
const baseMessages = conversation?.messages || [];
|
|
562
|
+
const system = baseSystemMessages.concat(prompt.system || []);
|
|
563
|
+
const combined = sanitizeMessages(baseMessages.concat(prompt.messages || []));
|
|
564
|
+
const mergedMessages = mergeConsecutiveUserMessages(combined);
|
|
565
|
+
return {
|
|
566
|
+
messages: mergedMessages,
|
|
567
|
+
system: system.length > 0 ? system : undefined,
|
|
568
|
+
};
|
|
569
|
+
}
|
|
570
|
+
|
|
571
|
+
export function claudeMessagesContainToolBlocks(messages: MessageParam[]): boolean {
|
|
572
|
+
for (const msg of messages) {
|
|
573
|
+
if (!Array.isArray(msg.content)) continue;
|
|
574
|
+
for (const block of msg.content) {
|
|
575
|
+
if (typeof block === 'object' && block !== null && 'type' in block) {
|
|
576
|
+
if (block.type === 'tool_use' || block.type === 'tool_result') return true;
|
|
577
|
+
}
|
|
578
|
+
}
|
|
579
|
+
}
|
|
580
|
+
return false;
|
|
581
|
+
}
|
|
582
|
+
|
|
583
|
+
export function convertClaudeToolBlocksToText(messages: MessageParam[]): MessageParam[] {
|
|
584
|
+
return messages.map((msg) => {
|
|
585
|
+
if (!Array.isArray(msg.content)) return msg;
|
|
586
|
+
let hasToolBlocks = false;
|
|
587
|
+
for (const block of msg.content) {
|
|
588
|
+
if (
|
|
589
|
+
typeof block === 'object' &&
|
|
590
|
+
block !== null &&
|
|
591
|
+
'type' in block &&
|
|
592
|
+
(block.type === 'tool_use' || block.type === 'tool_result')
|
|
593
|
+
) {
|
|
594
|
+
hasToolBlocks = true;
|
|
595
|
+
break;
|
|
596
|
+
}
|
|
597
|
+
}
|
|
598
|
+
if (!hasToolBlocks) return msg;
|
|
599
|
+
|
|
600
|
+
const newContent: MessageParam['content'] = [];
|
|
601
|
+
for (const block of msg.content) {
|
|
602
|
+
if (typeof block === 'string') {
|
|
603
|
+
newContent.push(block);
|
|
604
|
+
continue;
|
|
605
|
+
}
|
|
606
|
+
if (block.type === 'tool_use') {
|
|
607
|
+
const inputStr = block.input ? JSON.stringify(block.input) : '';
|
|
608
|
+
const truncated = inputStr.length > 500 ? `${inputStr.substring(0, 500)}...` : inputStr;
|
|
609
|
+
(newContent as Array<{ type: 'text'; text: string }>).push({
|
|
610
|
+
type: 'text',
|
|
611
|
+
text: `[Tool call: ${block.name}(${truncated})]`,
|
|
612
|
+
});
|
|
613
|
+
} else if (block.type === 'tool_result') {
|
|
614
|
+
let resultStr = 'No content';
|
|
615
|
+
if (typeof block.content === 'string') {
|
|
616
|
+
resultStr = block.content.length > 500 ? `${block.content.substring(0, 500)}...` : block.content;
|
|
617
|
+
} else if (Array.isArray(block.content)) {
|
|
618
|
+
const texts = block.content
|
|
619
|
+
.filter((c): c is { type: 'text'; text: string } => c.type === 'text')
|
|
620
|
+
.map((c) => (c.text.length > 500 ? `${c.text.substring(0, 500)}...` : c.text));
|
|
621
|
+
resultStr = texts.join('\n') || 'No text content';
|
|
622
|
+
}
|
|
623
|
+
(newContent as Array<{ type: 'text'; text: string }>).push({
|
|
624
|
+
type: 'text',
|
|
625
|
+
text: `[Tool result: ${resultStr}]`,
|
|
626
|
+
});
|
|
627
|
+
} else {
|
|
628
|
+
newContent.push(block as ContentBlockParam);
|
|
629
|
+
}
|
|
630
|
+
}
|
|
631
|
+
return { ...msg, content: newContent };
|
|
632
|
+
});
|
|
633
|
+
}
|
|
634
|
+
|
|
635
|
+
// ============================================================================
|
|
636
|
+
// Cache control stripping
|
|
637
|
+
// ============================================================================
|
|
638
|
+
|
|
639
|
+
function stripClaudeCacheControlFromBlock<T extends ContentBlockParam>(block: T): T {
|
|
640
|
+
if (typeof block === 'object' && block !== null && 'cache_control' in block) {
|
|
641
|
+
const { cache_control: _cc, ...rest } = block as T & { cache_control: unknown };
|
|
642
|
+
return rest as T;
|
|
643
|
+
}
|
|
644
|
+
return block;
|
|
645
|
+
}
|
|
646
|
+
|
|
647
|
+
function stripClaudeCacheControlFromMessages(messages: MessageParam[]): MessageParam[] {
|
|
648
|
+
return messages.map((msg) => {
|
|
649
|
+
if (!Array.isArray(msg.content)) return msg;
|
|
650
|
+
return { ...msg, content: msg.content.map(stripClaudeCacheControlFromBlock) };
|
|
651
|
+
});
|
|
652
|
+
}
|
|
653
|
+
|
|
654
|
+
function stripClaudeCacheControlFromSystem(system?: TextBlockParam[]): TextBlockParam[] | undefined {
|
|
655
|
+
if (!system) return undefined;
|
|
656
|
+
return system.map(stripClaudeCacheControlFromBlock);
|
|
657
|
+
}
|
|
658
|
+
|
|
659
|
+
function stripClaudeCacheControlFromTools(
|
|
660
|
+
tools?: MessageCreateParamsBase['tools'],
|
|
661
|
+
): MessageCreateParamsBase['tools'] | undefined {
|
|
662
|
+
if (!tools) return undefined;
|
|
663
|
+
return tools.map((tool) => {
|
|
664
|
+
if ('cache_control' in tool) {
|
|
665
|
+
const { cache_control: _cc, ...rest } = tool as ClaudeTool & { cache_control: unknown };
|
|
666
|
+
return rest as ClaudeTool;
|
|
667
|
+
}
|
|
668
|
+
return tool;
|
|
669
|
+
});
|
|
670
|
+
}
|
|
671
|
+
|
|
672
|
+
// ============================================================================
|
|
673
|
+
// Payload builder
|
|
674
|
+
// ============================================================================
|
|
675
|
+
|
|
676
|
+
export function getClaudePayload(
|
|
677
|
+
options: ExecutionOptions,
|
|
678
|
+
prompt: ClaudePrompt,
|
|
679
|
+
): { payload: MessageCreateParamsBase; requestOptions: RequestOptions | undefined } {
|
|
680
|
+
const modelName = options.model;
|
|
681
|
+
const model_options = options.model_options as ClaudeBaseOptions | undefined;
|
|
682
|
+
|
|
683
|
+
let requestOptions: RequestOptions | undefined;
|
|
684
|
+
if (
|
|
685
|
+
modelName.includes('claude-3-7-sonnet') &&
|
|
686
|
+
((model_options?.max_tokens ?? 0) > 64000 || (model_options?.thinking_budget_tokens ?? 0) > 64000)
|
|
687
|
+
) {
|
|
688
|
+
requestOptions = { headers: { 'anthropic-beta': 'output-128k-2025-02-19' } };
|
|
689
|
+
}
|
|
690
|
+
|
|
691
|
+
// Merge first so parallel tool results split across user messages are recombined
|
|
692
|
+
// into the single user turn after their assistant tool_use message; then fix both
|
|
693
|
+
// orphan directions (tool_use without result, and result without tool_use) so the
|
|
694
|
+
// request can never trip Anthropic/Vertex's tool_use/tool_result pairing validation.
|
|
695
|
+
const mergedMessages = mergeConsecutiveUserMessages(prompt.messages);
|
|
696
|
+
const fixedMessages = fixOrphanedToolResults(fixOrphanedToolUse(mergedMessages));
|
|
697
|
+
let sanitizedMessages = sanitizeMessages(fixedMessages);
|
|
698
|
+
|
|
699
|
+
if (options.tools) {
|
|
700
|
+
for (const tool of options.tools) {
|
|
701
|
+
if (tool.input_schema.type !== 'object') {
|
|
702
|
+
throw new Error(
|
|
703
|
+
`Tool "${tool.name}" has invalid input_schema.type: expected "object", got "${tool.input_schema.type}"`,
|
|
704
|
+
);
|
|
705
|
+
}
|
|
706
|
+
}
|
|
707
|
+
}
|
|
708
|
+
|
|
709
|
+
const hasTools = options.tools && options.tools.length > 0;
|
|
710
|
+
if (!hasTools && claudeMessagesContainToolBlocks(sanitizedMessages)) {
|
|
711
|
+
sanitizedMessages = convertClaudeToolBlocksToText(sanitizedMessages);
|
|
712
|
+
}
|
|
713
|
+
|
|
714
|
+
sanitizedMessages = stripClaudeCacheControlFromMessages(sanitizedMessages);
|
|
715
|
+
const sanitizedSystem = stripClaudeCacheControlFromSystem(prompt.system);
|
|
716
|
+
const sanitizedTools = hasTools
|
|
717
|
+
? stripClaudeCacheControlFromTools(options.tools as MessageCreateParamsBase['tools'])
|
|
718
|
+
: undefined;
|
|
719
|
+
|
|
720
|
+
const cacheEnabled = model_options?.cache_enabled === true;
|
|
721
|
+
if (cacheEnabled) {
|
|
722
|
+
const cacheTtl = model_options?.cache_ttl as '5m' | '1h' | undefined;
|
|
723
|
+
const cacheControl = { type: 'ephemeral' as const, ...(cacheTtl && { ttl: cacheTtl }) };
|
|
724
|
+
|
|
725
|
+
if (sanitizedSystem && sanitizedSystem.length > 0) {
|
|
726
|
+
const lastBlock = sanitizedSystem[sanitizedSystem.length - 1] as TextBlockParam & {
|
|
727
|
+
cache_control?: unknown;
|
|
728
|
+
};
|
|
729
|
+
lastBlock.cache_control = cacheControl;
|
|
730
|
+
}
|
|
731
|
+
if (sanitizedTools && sanitizedTools.length > 0) {
|
|
732
|
+
const lastTool = sanitizedTools[sanitizedTools.length - 1] as ClaudeTool & { cache_control?: unknown };
|
|
733
|
+
lastTool.cache_control = cacheControl;
|
|
734
|
+
}
|
|
735
|
+
if (sanitizedMessages.length >= 4) {
|
|
736
|
+
const pivotMsg = sanitizedMessages[sanitizedMessages.length - 2];
|
|
737
|
+
if (Array.isArray(pivotMsg.content) && pivotMsg.content.length > 0) {
|
|
738
|
+
const lastBlock = pivotMsg.content[pivotMsg.content.length - 1];
|
|
739
|
+
if (
|
|
740
|
+
typeof lastBlock === 'object' &&
|
|
741
|
+
lastBlock !== null &&
|
|
742
|
+
'type' in lastBlock &&
|
|
743
|
+
lastBlock.type !== 'thinking' &&
|
|
744
|
+
lastBlock.type !== 'redacted_thinking'
|
|
745
|
+
) {
|
|
746
|
+
(lastBlock as TextBlockParam).cache_control = cacheControl;
|
|
747
|
+
}
|
|
748
|
+
}
|
|
749
|
+
}
|
|
750
|
+
}
|
|
751
|
+
|
|
752
|
+
const { thinking, outputConfig, hasSamplingRestriction } = resolveClaudeThinking(
|
|
753
|
+
modelName,
|
|
754
|
+
model_options as Parameters<typeof resolveClaudeThinking>[1],
|
|
755
|
+
);
|
|
756
|
+
|
|
757
|
+
const payload: MessageCreateParamsBase = {
|
|
758
|
+
messages: sanitizedMessages,
|
|
759
|
+
system: sanitizedSystem,
|
|
760
|
+
tools: sanitizedTools,
|
|
761
|
+
temperature: hasSamplingRestriction ? undefined : model_options?.temperature,
|
|
762
|
+
model: modelName,
|
|
763
|
+
max_tokens: claudeMaxTokens(options),
|
|
764
|
+
top_p: hasSamplingRestriction
|
|
765
|
+
? undefined
|
|
766
|
+
: model_options?.temperature != null
|
|
767
|
+
? undefined
|
|
768
|
+
: model_options?.top_p,
|
|
769
|
+
top_k: hasSamplingRestriction ? undefined : model_options?.top_k,
|
|
770
|
+
stop_sequences: model_options?.stop_sequence,
|
|
771
|
+
thinking,
|
|
772
|
+
stream: true,
|
|
773
|
+
...(outputConfig && { output_config: outputConfig }),
|
|
774
|
+
};
|
|
775
|
+
|
|
776
|
+
return { payload, requestOptions };
|
|
777
|
+
}
|
|
778
|
+
|
|
779
|
+
// ============================================================================
|
|
780
|
+
// Streaming conversation builder (called after stream completes)
|
|
781
|
+
// ============================================================================
|
|
782
|
+
|
|
783
|
+
export function buildClaudeStreamingConversation(
|
|
784
|
+
prompt: ClaudePrompt,
|
|
785
|
+
result: unknown[],
|
|
786
|
+
toolUse: unknown[] | undefined,
|
|
787
|
+
options: ExecutionOptions,
|
|
788
|
+
): ClaudePrompt {
|
|
789
|
+
const completionResults = result as CompletionResult[];
|
|
790
|
+
const text = completionResults
|
|
791
|
+
.filter((r) => r.type === 'text')
|
|
792
|
+
.map((r) => r.value as string)
|
|
793
|
+
.join('');
|
|
794
|
+
|
|
795
|
+
let conversation = updateClaudeConversation(options.conversation as ClaudePrompt | undefined, prompt);
|
|
796
|
+
|
|
797
|
+
if (text) {
|
|
798
|
+
const assistantMsg: MessageParam = { role: 'assistant', content: text };
|
|
799
|
+
conversation = updateClaudeConversation(conversation, { messages: [assistantMsg] });
|
|
800
|
+
}
|
|
801
|
+
|
|
802
|
+
if (toolUse && toolUse.length > 0) {
|
|
803
|
+
const toolBlocks: ContentBlockParam[] = (toolUse as ToolUse[]).map((t) => ({
|
|
804
|
+
type: 'tool_use' as const,
|
|
805
|
+
id: t.id,
|
|
806
|
+
name: t.tool_name,
|
|
807
|
+
input: t.tool_input ?? {},
|
|
808
|
+
}));
|
|
809
|
+
const assistantToolMsg: MessageParam = { role: 'assistant', content: toolBlocks };
|
|
810
|
+
conversation = updateClaudeConversation(conversation, { messages: [assistantToolMsg] });
|
|
811
|
+
}
|
|
812
|
+
|
|
813
|
+
conversation = incrementConversationTurn(conversation) as ClaudePrompt;
|
|
814
|
+
const currentTurn = getConversationMeta(conversation).turnNumber;
|
|
815
|
+
// When text trimming is requested, keep the agent's active working set (the
|
|
816
|
+
// most recent messages — the file it just read/wrote, latest diagnostics)
|
|
817
|
+
// fully intact, and aggressively shrink large text only in OLDER messages.
|
|
818
|
+
// Clamp the effective cap so a lax caller value (e.g. 10000) still bites on
|
|
819
|
+
// stale blocks; long agent conversations otherwise balloon context and
|
|
820
|
+
// trigger frequent expensive checkpoints.
|
|
821
|
+
const requestedTextMax = options.stripTextMaxTokens;
|
|
822
|
+
const stripOptions = {
|
|
823
|
+
keepForTurns: options.stripImagesAfterTurns ?? Infinity,
|
|
824
|
+
currentTurn,
|
|
825
|
+
textMaxTokens: requestedTextMax ? Math.min(requestedTextMax, OLD_MESSAGE_TEXT_MAX_TOKENS) : undefined,
|
|
826
|
+
keepRecentMessages: requestedTextMax ? KEEP_RECENT_MESSAGES : undefined,
|
|
827
|
+
};
|
|
828
|
+
let processed = stripBase64ImagesFromConversation(conversation, stripOptions);
|
|
829
|
+
processed = truncateLargeTextInConversation(processed, stripOptions);
|
|
830
|
+
processed = stripHeartbeatsFromConversation(processed, {
|
|
831
|
+
keepForTurns: options.stripHeartbeatsAfterTurns ?? 1,
|
|
832
|
+
currentTurn,
|
|
833
|
+
});
|
|
834
|
+
return processed as ClaudePrompt;
|
|
835
|
+
}
|
|
836
|
+
|
|
837
|
+
// ============================================================================
|
|
838
|
+
// Execution helpers (standalone, take a client parameter)
|
|
839
|
+
// ============================================================================
|
|
840
|
+
|
|
841
|
+
/**
|
|
842
|
+
* Execute a non-streaming Claude completion.
|
|
843
|
+
* Works with any Anthropic-compatible client (Anthropic or AnthropicVertex).
|
|
844
|
+
*/
|
|
845
|
+
export async function executeClaudeCompletion(
|
|
846
|
+
client: Anthropic | AnthropicVertex,
|
|
847
|
+
prompt: ClaudePrompt,
|
|
848
|
+
options: ExecutionOptions,
|
|
849
|
+
): Promise<Completion> {
|
|
850
|
+
const model_options = options.model_options as ClaudeBaseOptions | undefined;
|
|
851
|
+
|
|
852
|
+
let conversation = updateClaudeConversation(options.conversation as ClaudePrompt | undefined, prompt);
|
|
853
|
+
|
|
854
|
+
const { payload, requestOptions } = getClaudePayload(options, conversation);
|
|
855
|
+
|
|
856
|
+
const responseStream = await streamClaudeMessages(client, payload as MessageStreamParams, requestOptions);
|
|
857
|
+
const result = await responseStream.finalMessage();
|
|
858
|
+
|
|
859
|
+
const includeThoughts = model_options?.include_thoughts ?? false;
|
|
860
|
+
const text = collectAllTextContent(result.content, includeThoughts);
|
|
861
|
+
const tool_use = collectClaudeTools(result.content);
|
|
862
|
+
|
|
863
|
+
conversation = updateClaudeConversation(conversation, createPromptFromResponse(result));
|
|
864
|
+
conversation = incrementConversationTurn(conversation) as ClaudePrompt;
|
|
865
|
+
const currentTurn = getConversationMeta(conversation).turnNumber;
|
|
866
|
+
const stripOpts = {
|
|
867
|
+
keepForTurns: options.stripImagesAfterTurns ?? Infinity,
|
|
868
|
+
currentTurn,
|
|
869
|
+
textMaxTokens: options.stripTextMaxTokens,
|
|
870
|
+
};
|
|
871
|
+
let processedConversation = stripBase64ImagesFromConversation(conversation, stripOpts);
|
|
872
|
+
processedConversation = truncateLargeTextInConversation(processedConversation, stripOpts);
|
|
873
|
+
processedConversation = stripHeartbeatsFromConversation(processedConversation, {
|
|
874
|
+
keepForTurns: options.stripHeartbeatsAfterTurns ?? 1,
|
|
875
|
+
currentTurn,
|
|
876
|
+
});
|
|
877
|
+
|
|
878
|
+
return {
|
|
879
|
+
result: text ? [{ type: 'text', value: text }] : [{ type: 'text', value: '' }],
|
|
880
|
+
tool_use,
|
|
881
|
+
token_usage: anthropicUsageToTokenUsage(result.usage),
|
|
882
|
+
finish_reason: tool_use ? 'tool_use' : claudeFinishReason(result?.stop_reason ?? ''),
|
|
883
|
+
conversation: processedConversation,
|
|
884
|
+
};
|
|
885
|
+
}
|
|
886
|
+
|
|
887
|
+
/**
|
|
888
|
+
* Execute a streaming Claude completion.
|
|
889
|
+
* Works with any Anthropic-compatible client (Anthropic or AnthropicVertex).
|
|
890
|
+
*/
|
|
891
|
+
export async function streamClaudeCompletion(
|
|
892
|
+
client: Anthropic | AnthropicVertex,
|
|
893
|
+
prompt: ClaudePrompt,
|
|
894
|
+
options: ExecutionOptions,
|
|
895
|
+
): Promise<AsyncIterable<CompletionChunkObject>> {
|
|
896
|
+
const model_options = options.model_options as ClaudeBaseOptions | undefined;
|
|
897
|
+
const conversation = updateClaudeConversation(options.conversation as ClaudePrompt | undefined, prompt);
|
|
898
|
+
|
|
899
|
+
const { payload, requestOptions } = getClaudePayload(options, conversation);
|
|
900
|
+
const streamingPayload: MessageStreamParams = { ...payload, stream: true };
|
|
901
|
+
|
|
902
|
+
const response_stream = await streamClaudeMessages(client, streamingPayload, requestOptions);
|
|
903
|
+
|
|
904
|
+
let currentToolUse: { id: string; name: string; inputJson: string } | null = null;
|
|
905
|
+
let pendingSpacing = false;
|
|
906
|
+
|
|
907
|
+
const stream = asyncMap(response_stream, async (streamEvent: RawMessageStreamEvent) => {
|
|
908
|
+
switch (streamEvent.type) {
|
|
909
|
+
case 'message_start':
|
|
910
|
+
return {
|
|
911
|
+
result: [{ type: 'text', value: '' }],
|
|
912
|
+
token_usage: anthropicUsageToTokenUsage(streamEvent.message.usage as AnthropicUsageLike),
|
|
913
|
+
} satisfies CompletionChunkObject;
|
|
914
|
+
case 'message_delta':
|
|
915
|
+
return {
|
|
916
|
+
result: [{ type: 'text', value: '' }],
|
|
917
|
+
token_usage: { result: streamEvent.usage.output_tokens },
|
|
918
|
+
finish_reason: claudeFinishReason(streamEvent.delta.stop_reason ?? undefined),
|
|
919
|
+
} satisfies CompletionChunkObject;
|
|
920
|
+
case 'content_block_start':
|
|
921
|
+
if (streamEvent.content_block.type === 'tool_use') {
|
|
922
|
+
currentToolUse = {
|
|
923
|
+
id: streamEvent.content_block.id,
|
|
924
|
+
name: streamEvent.content_block.name,
|
|
925
|
+
inputJson: '',
|
|
926
|
+
};
|
|
927
|
+
return {
|
|
928
|
+
result: [],
|
|
929
|
+
tool_use: [
|
|
930
|
+
{
|
|
931
|
+
id: streamEvent.content_block.id,
|
|
932
|
+
tool_name: streamEvent.content_block.name,
|
|
933
|
+
tool_input: '' as unknown as JSONObject,
|
|
934
|
+
},
|
|
935
|
+
],
|
|
936
|
+
} satisfies CompletionChunkObject;
|
|
937
|
+
}
|
|
938
|
+
if (streamEvent.content_block.type === 'redacted_thinking' && model_options?.include_thoughts) {
|
|
939
|
+
return {
|
|
940
|
+
result: [{ type: 'text', value: `[Redacted thinking: ${streamEvent.content_block.data}]` }],
|
|
941
|
+
} satisfies CompletionChunkObject;
|
|
942
|
+
}
|
|
943
|
+
break;
|
|
944
|
+
case 'content_block_delta':
|
|
945
|
+
switch (streamEvent.delta.type) {
|
|
946
|
+
case 'text_delta': {
|
|
947
|
+
const prefix = pendingSpacing ? '\n\n' : '';
|
|
948
|
+
pendingSpacing = false;
|
|
949
|
+
return {
|
|
950
|
+
result: streamEvent.delta.text
|
|
951
|
+
? [{ type: 'text', value: prefix + streamEvent.delta.text }]
|
|
952
|
+
: [],
|
|
953
|
+
} satisfies CompletionChunkObject;
|
|
954
|
+
}
|
|
955
|
+
case 'input_json_delta':
|
|
956
|
+
if (currentToolUse && streamEvent.delta.partial_json) {
|
|
957
|
+
return {
|
|
958
|
+
result: [],
|
|
959
|
+
tool_use: [
|
|
960
|
+
{
|
|
961
|
+
id: currentToolUse.id,
|
|
962
|
+
tool_name: '',
|
|
963
|
+
tool_input: streamEvent.delta.partial_json as unknown as JSONObject,
|
|
964
|
+
},
|
|
965
|
+
],
|
|
966
|
+
} satisfies CompletionChunkObject;
|
|
967
|
+
}
|
|
968
|
+
break;
|
|
969
|
+
case 'thinking_delta':
|
|
970
|
+
if (model_options?.include_thoughts) {
|
|
971
|
+
return {
|
|
972
|
+
result: streamEvent.delta.thinking
|
|
973
|
+
? [{ type: 'text', value: streamEvent.delta.thinking }]
|
|
974
|
+
: [],
|
|
975
|
+
} satisfies CompletionChunkObject;
|
|
976
|
+
}
|
|
977
|
+
break;
|
|
978
|
+
case 'signature_delta':
|
|
979
|
+
if (model_options?.include_thoughts) {
|
|
980
|
+
pendingSpacing = true;
|
|
981
|
+
}
|
|
982
|
+
break;
|
|
983
|
+
}
|
|
984
|
+
break;
|
|
985
|
+
case 'content_block_stop':
|
|
986
|
+
if (currentToolUse) {
|
|
987
|
+
currentToolUse = null;
|
|
988
|
+
pendingSpacing = false;
|
|
989
|
+
}
|
|
990
|
+
break;
|
|
991
|
+
}
|
|
992
|
+
|
|
993
|
+
return { result: [] } satisfies CompletionChunkObject;
|
|
994
|
+
});
|
|
995
|
+
|
|
996
|
+
return stream;
|
|
997
|
+
}
|
|
998
|
+
|
|
999
|
+
// ============================================================================
|
|
1000
|
+
// Error handling
|
|
1001
|
+
// ============================================================================
|
|
1002
|
+
|
|
1003
|
+
export function formatAnthropicLlumiverseError(error: unknown, context: LlumiverseErrorContext): LlumiverseError {
|
|
1004
|
+
if (error instanceof AnthropicError && !(error instanceof APIError)) {
|
|
1005
|
+
// Client-side SDK error (e.g. "Streaming is required for operations that may take longer than 10 minutes").
|
|
1006
|
+
// These are structural/configuration errors — retrying will never succeed.
|
|
1007
|
+
const errorName = error.constructor?.name || 'AnthropicError';
|
|
1008
|
+
return new LlumiverseError(
|
|
1009
|
+
`[${context.provider}] ${error.message}`,
|
|
1010
|
+
false,
|
|
1011
|
+
context,
|
|
1012
|
+
error,
|
|
1013
|
+
undefined,
|
|
1014
|
+
errorName,
|
|
1015
|
+
);
|
|
1016
|
+
}
|
|
1017
|
+
if (!(error instanceof APIError)) {
|
|
1018
|
+
// Not an Anthropic error — rethrow for default handling
|
|
1019
|
+
throw error;
|
|
1020
|
+
}
|
|
1021
|
+
|
|
1022
|
+
const apiError = error as APIError;
|
|
1023
|
+
const httpStatusCode = apiError.status;
|
|
1024
|
+
let message = apiError.message || String(error);
|
|
1025
|
+
let errorType: string | undefined;
|
|
1026
|
+
|
|
1027
|
+
if (apiError.error && typeof apiError.error === 'object') {
|
|
1028
|
+
const nested = apiError.error as Record<string, unknown>;
|
|
1029
|
+
if (nested.error && typeof nested.error === 'object') {
|
|
1030
|
+
const innerError = nested.error as Record<string, unknown>;
|
|
1031
|
+
errorType = innerError.type as string | undefined;
|
|
1032
|
+
if (typeof innerError.message === 'string') {
|
|
1033
|
+
message = innerError.message;
|
|
1034
|
+
}
|
|
1035
|
+
}
|
|
1036
|
+
}
|
|
1037
|
+
|
|
1038
|
+
let userMessage = message;
|
|
1039
|
+
if (httpStatusCode) userMessage = `[${httpStatusCode}] ${userMessage}`;
|
|
1040
|
+
if (errorType && errorType !== 'error') userMessage = `${errorType}: ${userMessage}`;
|
|
1041
|
+
if (apiError.requestID) userMessage += ` (Request ID: ${apiError.requestID})`;
|
|
1042
|
+
|
|
1043
|
+
const retryable = isClaudeErrorRetryable(error, httpStatusCode, errorType, apiError.headers ?? undefined);
|
|
1044
|
+
const errorName = error.constructor?.name || 'AnthropicError';
|
|
1045
|
+
|
|
1046
|
+
return new LlumiverseError(
|
|
1047
|
+
`[${context.provider}] ${userMessage}`,
|
|
1048
|
+
retryable,
|
|
1049
|
+
context,
|
|
1050
|
+
error,
|
|
1051
|
+
httpStatusCode,
|
|
1052
|
+
errorName,
|
|
1053
|
+
);
|
|
1054
|
+
}
|
|
1055
|
+
|
|
1056
|
+
export function isClaudeErrorRetryable(
|
|
1057
|
+
error: unknown,
|
|
1058
|
+
httpStatusCode: number | undefined,
|
|
1059
|
+
errorType: string | undefined,
|
|
1060
|
+
headers?: Headers | undefined,
|
|
1061
|
+
): boolean | undefined {
|
|
1062
|
+
// Honour the server's explicit retry directive first (mirrors SDK shouldRetry logic).
|
|
1063
|
+
const shouldRetryHeader = headers?.get('x-should-retry');
|
|
1064
|
+
if (shouldRetryHeader === 'true') return true;
|
|
1065
|
+
if (shouldRetryHeader === 'false') return false;
|
|
1066
|
+
|
|
1067
|
+
if (error instanceof APIUserAbortError) return false;
|
|
1068
|
+
if (error instanceof RateLimitError) return true;
|
|
1069
|
+
if (error instanceof InternalServerError) return true;
|
|
1070
|
+
if (error instanceof APIConnectionTimeoutError) return true;
|
|
1071
|
+
if (error instanceof BadRequestError) return false;
|
|
1072
|
+
if (error instanceof AuthenticationError) return false;
|
|
1073
|
+
if (error instanceof PermissionDeniedError) return false;
|
|
1074
|
+
if (error instanceof NotFoundError) return false;
|
|
1075
|
+
if (error instanceof ConflictError) return true; // SDK retries 409 (lock timeouts)
|
|
1076
|
+
if (error instanceof UnprocessableEntityError) return false;
|
|
1077
|
+
if (errorType === 'invalid_request_error') return false;
|
|
1078
|
+
if (httpStatusCode !== undefined) {
|
|
1079
|
+
if (httpStatusCode === 429 || httpStatusCode === 408 || httpStatusCode === 529) return true;
|
|
1080
|
+
if (httpStatusCode >= 500 && httpStatusCode < 600) return true;
|
|
1081
|
+
if (httpStatusCode >= 400 && httpStatusCode < 500) return false;
|
|
1082
|
+
}
|
|
1083
|
+
if (error instanceof APIConnectionError && !(error instanceof APIConnectionTimeoutError)) return true;
|
|
1084
|
+
return undefined;
|
|
1085
|
+
}
|