@opencode/ai 0.0.0-reserved → 2.0.0
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/README.md +879 -2
- package/dist/cache-policy.d.ts +2 -0
- package/dist/cache-policy.js +129 -0
- package/dist/image-client.d.ts +19 -0
- package/dist/image-client.js +28 -0
- package/dist/image.d.ts +110 -0
- package/dist/image.js +103 -0
- package/dist/index.d.ts +18 -0
- package/dist/index.js +12 -0
- package/dist/llm.d.ts +252 -0
- package/dist/llm.js +74 -0
- package/dist/protocols/alibaba-chat.d.ts +225 -0
- package/dist/protocols/alibaba-chat.js +75 -0
- package/dist/protocols/alibaba-messages.d.ts +350 -0
- package/dist/protocols/alibaba-messages.js +40 -0
- package/dist/protocols/alibaba-responses.d.ts +255 -0
- package/dist/protocols/alibaba-responses.js +80 -0
- package/dist/protocols/anthropic-messages.d.ts +980 -0
- package/dist/protocols/anthropic-messages.js +1460 -0
- package/dist/protocols/bedrock-converse.d.ts +499 -0
- package/dist/protocols/bedrock-converse.js +643 -0
- package/dist/protocols/bedrock-event-stream.d.ts +9 -0
- package/dist/protocols/bedrock-event-stream.js +97 -0
- package/dist/protocols/gemini.d.ts +285 -0
- package/dist/protocols/gemini.js +671 -0
- package/dist/protocols/google-images.d.ts +30 -0
- package/dist/protocols/google-images.js +181 -0
- package/dist/protocols/index.d.ts +12 -0
- package/dist/protocols/index.js +12 -0
- package/dist/protocols/meta-images.d.ts +26 -0
- package/dist/protocols/meta-images.js +89 -0
- package/dist/protocols/meta-messages.d.ts +356 -0
- package/dist/protocols/meta-messages.js +41 -0
- package/dist/protocols/meta-responses.d.ts +473 -0
- package/dist/protocols/meta-responses.js +173 -0
- package/dist/protocols/mistral-chat.d.ts +540 -0
- package/dist/protocols/mistral-chat.js +624 -0
- package/dist/protocols/open-responses-channel.d.ts +25 -0
- package/dist/protocols/open-responses-channel.js +141 -0
- package/dist/protocols/open-responses-continuation.d.ts +16 -0
- package/dist/protocols/open-responses-continuation.js +170 -0
- package/dist/protocols/open-responses.d.ts +1715 -0
- package/dist/protocols/open-responses.js +1228 -0
- package/dist/protocols/openai-chat.d.ts +860 -0
- package/dist/protocols/openai-chat.js +966 -0
- package/dist/protocols/openai-compatible-chat.d.ts +109 -0
- package/dist/protocols/openai-compatible-chat.js +19 -0
- package/dist/protocols/openai-compatible-responses.d.ts +172 -0
- package/dist/protocols/openai-compatible-responses.js +18 -0
- package/dist/protocols/openai-images.d.ts +32 -0
- package/dist/protocols/openai-images.js +183 -0
- package/dist/protocols/openai-responses.d.ts +1389 -0
- package/dist/protocols/openai-responses.js +260 -0
- package/dist/protocols/shared.d.ts +193 -0
- package/dist/protocols/shared.js +290 -0
- package/dist/protocols/utils/bedrock-auth.d.ts +60 -0
- package/dist/protocols/utils/bedrock-auth.js +85 -0
- package/dist/protocols/utils/bedrock-cache.d.ts +19 -0
- package/dist/protocols/utils/bedrock-cache.js +49 -0
- package/dist/protocols/utils/bedrock-media.d.ts +55 -0
- package/dist/protocols/utils/bedrock-media.js +73 -0
- package/dist/protocols/utils/cache.d.ts +6 -0
- package/dist/protocols/utils/cache.js +5 -0
- package/dist/protocols/utils/gemini-tool-schema.d.ts +2 -0
- package/dist/protocols/utils/gemini-tool-schema.js +103 -0
- package/dist/protocols/utils/image-input.d.ts +21 -0
- package/dist/protocols/utils/image-input.js +20 -0
- package/dist/protocols/utils/lifecycle.d.ts +24 -0
- package/dist/protocols/utils/lifecycle.js +72 -0
- package/dist/protocols/utils/meta-image.d.ts +2 -0
- package/dist/protocols/utils/meta-image.js +13 -0
- package/dist/protocols/utils/open-responses-options.d.ts +56 -0
- package/dist/protocols/utils/open-responses-options.js +61 -0
- package/dist/protocols/utils/openai-image.d.ts +5 -0
- package/dist/protocols/utils/openai-image.js +18 -0
- package/dist/protocols/utils/openai-options.d.ts +16 -0
- package/dist/protocols/utils/openai-options.js +14 -0
- package/dist/protocols/utils/partial-json-options.d.ts +62 -0
- package/dist/protocols/utils/partial-json-options.js +52 -0
- package/dist/protocols/utils/partial-json.d.ts +8 -0
- package/dist/protocols/utils/partial-json.js +279 -0
- package/dist/protocols/utils/responses-checkpoint.d.ts +4 -0
- package/dist/protocols/utils/responses-checkpoint.js +107 -0
- package/dist/protocols/utils/responses-compaction.d.ts +4 -0
- package/dist/protocols/utils/responses-compaction.js +129 -0
- package/dist/protocols/utils/responses-hosted-tools.d.ts +26 -0
- package/dist/protocols/utils/responses-hosted-tools.js +32 -0
- package/dist/protocols/utils/tool-schema.d.ts +8 -0
- package/dist/protocols/utils/tool-schema.js +54 -0
- package/dist/protocols/utils/tool-stream.d.ts +710 -0
- package/dist/protocols/utils/tool-stream.js +155 -0
- package/dist/protocols/xai-images.d.ts +26 -0
- package/dist/protocols/xai-images.js +106 -0
- package/dist/protocols/xai-responses.d.ts +250 -0
- package/dist/protocols/xai-responses.js +81 -0
- package/dist/protocols/zai-chat.d.ts +202 -0
- package/dist/protocols/zai-chat.js +49 -0
- package/dist/protocols/zai-images.d.ts +22 -0
- package/dist/protocols/zai-images.js +79 -0
- package/dist/protocols/zai-messages.d.ts +344 -0
- package/dist/protocols/zai-messages.js +27 -0
- package/dist/protocols.d.ts +1 -0
- package/dist/protocols.js +1 -0
- package/dist/provider-error.d.ts +15 -0
- package/dist/provider-error.js +125 -0
- package/dist/provider-package.d.ts +15 -0
- package/dist/provider-package.js +1 -0
- package/dist/provider.d.ts +23 -0
- package/dist/provider.js +2 -0
- package/dist/providers/alibaba/chat.d.ts +1 -0
- package/dist/providers/alibaba/chat.js +1 -0
- package/dist/providers/alibaba/messages.d.ts +3 -0
- package/dist/providers/alibaba/messages.js +1 -0
- package/dist/providers/alibaba/responses.d.ts +3 -0
- package/dist/providers/alibaba/responses.js +1 -0
- package/dist/providers/alibaba.d.ts +711 -0
- package/dist/providers/alibaba.js +94 -0
- package/dist/providers/amazon-bedrock/mantle/chat.d.ts +2 -0
- package/dist/providers/amazon-bedrock/mantle/chat.js +1 -0
- package/dist/providers/amazon-bedrock/mantle/responses.d.ts +2 -0
- package/dist/providers/amazon-bedrock/mantle/responses.js +1 -0
- package/dist/providers/amazon-bedrock/mantle.d.ts +2 -0
- package/dist/providers/amazon-bedrock/mantle.js +1 -0
- package/dist/providers/amazon-bedrock-mantle.d.ts +313 -0
- package/dist/providers/amazon-bedrock-mantle.js +79 -0
- package/dist/providers/amazon-bedrock.d.ts +173 -0
- package/dist/providers/amazon-bedrock.js +41 -0
- package/dist/providers/anthropic-compatible.d.ts +309 -0
- package/dist/providers/anthropic-compatible.js +50 -0
- package/dist/providers/anthropic.d.ts +307 -0
- package/dist/providers/anthropic.js +42 -0
- package/dist/providers/azure/chat.d.ts +2 -0
- package/dist/providers/azure/chat.js +1 -0
- package/dist/providers/azure/responses.d.ts +2 -0
- package/dist/providers/azure/responses.js +1 -0
- package/dist/providers/azure.d.ts +598 -0
- package/dist/providers/azure.js +115 -0
- package/dist/providers/baseten.d.ts +228 -0
- package/dist/providers/baseten.js +38 -0
- package/dist/providers/cerebras.d.ts +227 -0
- package/dist/providers/cerebras.js +40 -0
- package/dist/providers/cloudflare-ai-gateway.d.ts +237 -0
- package/dist/providers/cloudflare-ai-gateway.js +65 -0
- package/dist/providers/cloudflare-workers-ai.d.ts +231 -0
- package/dist/providers/cloudflare-workers-ai.js +51 -0
- package/dist/providers/deepinfra.d.ts +227 -0
- package/dist/providers/deepinfra.js +43 -0
- package/dist/providers/deepseek.d.ts +228 -0
- package/dist/providers/deepseek.js +41 -0
- package/dist/providers/fireworks.d.ts +228 -0
- package/dist/providers/fireworks.js +38 -0
- package/dist/providers/google-vertex/chat.d.ts +2 -0
- package/dist/providers/google-vertex/chat.js +1 -0
- package/dist/providers/google-vertex/gemini.d.ts +2 -0
- package/dist/providers/google-vertex/gemini.js +1 -0
- package/dist/providers/google-vertex/messages.d.ts +2 -0
- package/dist/providers/google-vertex/messages.js +1 -0
- package/dist/providers/google-vertex/responses.d.ts +2 -0
- package/dist/providers/google-vertex/responses.js +1 -0
- package/dist/providers/google-vertex-chat.d.ts +132 -0
- package/dist/providers/google-vertex-chat.js +55 -0
- package/dist/providers/google-vertex-messages.d.ts +306 -0
- package/dist/providers/google-vertex-messages.js +75 -0
- package/dist/providers/google-vertex-responses.d.ts +197 -0
- package/dist/providers/google-vertex-responses.js +56 -0
- package/dist/providers/google-vertex-shared.d.ts +22 -0
- package/dist/providers/google-vertex-shared.js +72 -0
- package/dist/providers/google-vertex.d.ts +119 -0
- package/dist/providers/google-vertex.js +98 -0
- package/dist/providers/google.d.ts +114 -0
- package/dist/providers/google.js +42 -0
- package/dist/providers/groq.d.ts +314 -0
- package/dist/providers/groq.js +81 -0
- package/dist/providers/index.d.ts +31 -0
- package/dist/providers/index.js +31 -0
- package/dist/providers/meta/chat.d.ts +2 -0
- package/dist/providers/meta/chat.js +1 -0
- package/dist/providers/meta/messages.d.ts +2 -0
- package/dist/providers/meta/messages.js +1 -0
- package/dist/providers/meta/responses.d.ts +2 -0
- package/dist/providers/meta/responses.js +1 -0
- package/dist/providers/meta.d.ts +636 -0
- package/dist/providers/meta.js +113 -0
- package/dist/providers/minimax/chat.d.ts +4 -0
- package/dist/providers/minimax/chat.js +8 -0
- package/dist/providers/minimax/messages.d.ts +1 -0
- package/dist/providers/minimax/messages.js +1 -0
- package/dist/providers/minimax/responses.d.ts +4 -0
- package/dist/providers/minimax/responses.js +8 -0
- package/dist/providers/minimax.d.ts +598 -0
- package/dist/providers/minimax.js +89 -0
- package/dist/providers/mistral.d.ts +207 -0
- package/dist/providers/mistral.js +28 -0
- package/dist/providers/moonshot/chat.d.ts +1 -0
- package/dist/providers/moonshot/chat.js +1 -0
- package/dist/providers/moonshot/messages.d.ts +4 -0
- package/dist/providers/moonshot/messages.js +8 -0
- package/dist/providers/moonshot/responses.d.ts +4 -0
- package/dist/providers/moonshot/responses.js +8 -0
- package/dist/providers/moonshot.d.ts +596 -0
- package/dist/providers/moonshot.js +90 -0
- package/dist/providers/open-responses-options.d.ts +6 -0
- package/dist/providers/open-responses-options.js +1 -0
- package/dist/providers/openai/chat.d.ts +2 -0
- package/dist/providers/openai/chat.js +1 -0
- package/dist/providers/openai/responses.d.ts +2 -0
- package/dist/providers/openai/responses.js +1 -0
- package/dist/providers/openai-compatible/responses.d.ts +1 -0
- package/dist/providers/openai-compatible/responses.js +1 -0
- package/dist/providers/openai-compatible-responses.d.ts +195 -0
- package/dist/providers/openai-compatible-responses.js +32 -0
- package/dist/providers/openai-compatible.d.ts +130 -0
- package/dist/providers/openai-compatible.js +33 -0
- package/dist/providers/openai-options.d.ts +26 -0
- package/dist/providers/openai-options.js +41 -0
- package/dist/providers/openai.d.ts +404 -0
- package/dist/providers/openai.js +82 -0
- package/dist/providers/openrouter.d.ts +563 -0
- package/dist/providers/openrouter.js +114 -0
- package/dist/providers/togetherai.d.ts +227 -0
- package/dist/providers/togetherai.js +40 -0
- package/dist/providers/xai.d.ts +163 -0
- package/dist/providers/xai.js +88 -0
- package/dist/providers/zai/chat.d.ts +1 -0
- package/dist/providers/zai/chat.js +1 -0
- package/dist/providers/zai-coding-plan/chat.d.ts +1 -0
- package/dist/providers/zai-coding-plan/chat.js +1 -0
- package/dist/providers/zai-coding-plan/messages.d.ts +4 -0
- package/dist/providers/zai-coding-plan/messages.js +8 -0
- package/dist/providers/zai-coding-plan/responses.d.ts +4 -0
- package/dist/providers/zai-coding-plan/responses.js +8 -0
- package/dist/providers/zai-coding-plan.d.ts +577 -0
- package/dist/providers/zai-coding-plan.js +63 -0
- package/dist/providers/zai.d.ts +149 -0
- package/dist/providers/zai.js +53 -0
- package/dist/providers.d.ts +1 -0
- package/dist/providers.js +1 -0
- package/dist/route/auth-options.d.ts +34 -0
- package/dist/route/auth-options.js +14 -0
- package/dist/route/auth.d.ts +51 -0
- package/dist/route/auth.js +87 -0
- package/dist/route/client.d.ts +430 -0
- package/dist/route/client.js +307 -0
- package/dist/route/endpoint.d.ts +28 -0
- package/dist/route/endpoint.js +21 -0
- package/dist/route/executor.d.ts +27 -0
- package/dist/route/executor.js +183 -0
- package/dist/route/framing.d.ts +29 -0
- package/dist/route/framing.js +14 -0
- package/dist/route/index.d.ts +16 -0
- package/dist/route/index.js +9 -0
- package/dist/route/protocol.d.ts +76 -0
- package/dist/route/protocol.js +16 -0
- package/dist/route/transport/http.d.ts +34 -0
- package/dist/route/transport/http.js +68 -0
- package/dist/route/transport/index.d.ts +41 -0
- package/dist/route/transport/index.js +4 -0
- package/dist/route/transport/websocket-channel.d.ts +57 -0
- package/dist/route/transport/websocket-channel.js +1 -0
- package/dist/route/transport/websocket.d.ts +56 -0
- package/dist/route/transport/websocket.js +370 -0
- package/dist/route.d.ts +1 -0
- package/dist/route.js +1 -0
- package/dist/schema/errors.d.ts +170 -0
- package/dist/schema/errors.js +131 -0
- package/dist/schema/events.d.ts +8123 -0
- package/dist/schema/events.js +550 -0
- package/dist/schema/ids.d.ts +17 -0
- package/dist/schema/ids.js +10 -0
- package/dist/schema/index.d.ts +5 -0
- package/dist/schema/index.js +5 -0
- package/dist/schema/messages.d.ts +646 -0
- package/dist/schema/messages.js +301 -0
- package/dist/schema/options.d.ts +159 -0
- package/dist/schema/options.js +189 -0
- package/dist/testing.d.ts +1693 -0
- package/dist/testing.js +129 -0
- package/dist/tool-history.d.ts +2 -0
- package/dist/tool-history.js +84 -0
- package/dist/tool-runtime.d.ts +15 -0
- package/dist/tool-runtime.js +62 -0
- package/dist/tool.d.ts +136 -0
- package/dist/tool.js +66 -0
- package/dist/utils/record.d.ts +2 -0
- package/dist/utils/record.js +2 -0
- package/dist/utils/sanitize.d.ts +1 -0
- package/dist/utils/sanitize.js +12 -0
- package/package.json +43 -10
|
@@ -0,0 +1,290 @@
|
|
|
1
|
+
import { Buffer } from "node:buffer";
|
|
2
|
+
import { Tool } from "@opencode/schema/tool";
|
|
3
|
+
import { Effect, Schema, Stream } from "effect";
|
|
4
|
+
import * as Sse from "effect/unstable/encoding/Sse";
|
|
5
|
+
import { Headers, HttpClientRequest, HttpClientResponse } from "effect/unstable/http";
|
|
6
|
+
import { InvalidProviderOutputError, InvalidRequestError, UnsupportedOperationError, AIError, HttpContext, LLMRequest, Message, ToolDefinition, } from "../schema/index.js";
|
|
7
|
+
import { isRecord } from "../utils/record.js";
|
|
8
|
+
export { isRecord };
|
|
9
|
+
export const Json = Schema.fromJsonString(Schema.Unknown);
|
|
10
|
+
export const decodeJson = Schema.decodeUnknownSync(Json);
|
|
11
|
+
export const encodeJson = Schema.encodeSync(Json);
|
|
12
|
+
const isJson = Schema.is(Schema.Json);
|
|
13
|
+
export const JsonObject = Schema.Record(Schema.String, Schema.Unknown);
|
|
14
|
+
export const optionalArray = (schema) => Schema.optional(Schema.Array(schema));
|
|
15
|
+
export const optionalNull = (schema) => Schema.optional(Schema.NullOr(schema));
|
|
16
|
+
export const OPENAI_PROMPT_CACHE_KEY_MAX_LENGTH = 64;
|
|
17
|
+
// OpenAI limits `prompt_cache_key` to 64 chars; DeepSeek and Zai inherit the same
|
|
18
|
+
// limit via their OpenAI-compatible APIs. Clamp with unicode-aware slicing.
|
|
19
|
+
export const promptCacheKey = (request) => {
|
|
20
|
+
if (request.cache === "none" || request.promptCacheKey === undefined)
|
|
21
|
+
return undefined;
|
|
22
|
+
const chars = Array.from(request.promptCacheKey);
|
|
23
|
+
if (chars.length <= OPENAI_PROMPT_CACHE_KEY_MAX_LENGTH)
|
|
24
|
+
return request.promptCacheKey;
|
|
25
|
+
return chars.slice(0, OPENAI_PROMPT_CACHE_KEY_MAX_LENGTH).join("");
|
|
26
|
+
};
|
|
27
|
+
/**
|
|
28
|
+
* `Usage.totalTokens` policy shared by every route. Honors a provider-
|
|
29
|
+
* supplied total; otherwise falls back to `inputTokens + outputTokens` only
|
|
30
|
+
* when at least one is defined. Returns `undefined` when neither input nor
|
|
31
|
+
* output is known so routes don't publish a misleading `0`.
|
|
32
|
+
*
|
|
33
|
+
* Under the inclusive `AI.Usage` contract, `inputTokens` includes cached input
|
|
34
|
+
* and `outputTokens` includes reasoning. Protocol mappers normalize those
|
|
35
|
+
* inclusive values before calling this helper. The provider-supplied total is
|
|
36
|
+
* the source of truth when present; otherwise their sum is the canonical total.
|
|
37
|
+
*/
|
|
38
|
+
export const totalTokens = (inputTokens, outputTokens, total) => {
|
|
39
|
+
if (total !== undefined)
|
|
40
|
+
return total;
|
|
41
|
+
if (inputTokens === undefined && outputTokens === undefined)
|
|
42
|
+
return undefined;
|
|
43
|
+
return (inputTokens ?? 0) + (outputTokens ?? 0);
|
|
44
|
+
};
|
|
45
|
+
/**
|
|
46
|
+
* Subtract `subtrahend` from `total`, clamping to zero if the provider
|
|
47
|
+
* reports a non-sensical breakdown (e.g. `cached_tokens > prompt_tokens`).
|
|
48
|
+
* Used by protocol mappers when deriving a non-overlapping breakdown field
|
|
49
|
+
* from a provider's inclusive total — `nonCachedInputTokens` from
|
|
50
|
+
* `inputTokens - cacheReadInputTokens - cacheWriteInputTokens`.
|
|
51
|
+
*
|
|
52
|
+
* If `total` is `undefined`, returns `undefined` (we don't fabricate
|
|
53
|
+
* counts). If `subtrahend` is `undefined`, returns `total` unchanged. The
|
|
54
|
+
* provider-native breakdown stays available on `Usage.providerMetadata` for debugging.
|
|
55
|
+
*/
|
|
56
|
+
export const subtractTokens = (total, subtrahend) => {
|
|
57
|
+
if (total === undefined)
|
|
58
|
+
return undefined;
|
|
59
|
+
if (subtrahend === undefined)
|
|
60
|
+
return total;
|
|
61
|
+
return Math.max(0, total - subtrahend);
|
|
62
|
+
};
|
|
63
|
+
/**
|
|
64
|
+
* Sum a list of optional token counts, returning `undefined` only when
|
|
65
|
+
* every value is `undefined` (so we don't fabricate a `0`). Used by
|
|
66
|
+
* protocol mappers to derive the inclusive `inputTokens` total from a
|
|
67
|
+
* provider that natively reports a non-overlapping breakdown
|
|
68
|
+
* (e.g. Anthropic, whose `input_tokens` is already non-cached only).
|
|
69
|
+
*/
|
|
70
|
+
export const sumTokens = (...values) => {
|
|
71
|
+
if (values.every((value) => value === undefined))
|
|
72
|
+
return undefined;
|
|
73
|
+
return values.reduce((acc, value) => acc + (value ?? 0), 0);
|
|
74
|
+
};
|
|
75
|
+
export const eventError = (route, message, body, cause) => new AIError({
|
|
76
|
+
reason: new InvalidProviderOutputError({ route, message, body, cause }),
|
|
77
|
+
});
|
|
78
|
+
export const parseJson = (route, input, message) => Effect.try({
|
|
79
|
+
try: () => decodeJson(input),
|
|
80
|
+
catch: (cause) => eventError(route, message, input, cause),
|
|
81
|
+
});
|
|
82
|
+
/**
|
|
83
|
+
* Join the `text` field of a list of parts with newlines. Used by routes
|
|
84
|
+
* that flatten system / message content arrays into a single provider string
|
|
85
|
+
* (OpenAI Chat `system` content, OpenAI Responses `system` content, Gemini
|
|
86
|
+
* `systemInstruction.parts[].text`).
|
|
87
|
+
*/
|
|
88
|
+
export const joinText = (parts) => parts.map((part) => part.text).join("\n");
|
|
89
|
+
const escapeSystemUpdateText = (text) => text.replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">");
|
|
90
|
+
/**
|
|
91
|
+
* Stable fallback representation for chronological `Message.system(...)`
|
|
92
|
+
* updates on routes that do not support that privileged role natively. The
|
|
93
|
+
* wrapper remains visibly lower-authority user text, preserves the original
|
|
94
|
+
* temporal position, and XML-escapes content so it cannot close the wrapper.
|
|
95
|
+
*/
|
|
96
|
+
export const wrapSystemUpdate = (parts) => `<system-update>\n${escapeSystemUpdateText(joinText(parts))}\n</system-update>`;
|
|
97
|
+
/**
|
|
98
|
+
* Chronological system updates deliberately accept text only. Do not insert
|
|
99
|
+
* raw retrieved, tool, or web content into privileged updates: keep untrusted
|
|
100
|
+
* data in ordinary user/tool messages instead.
|
|
101
|
+
*/
|
|
102
|
+
export const systemUpdateText = Effect.fn("ProviderShared.systemUpdateText")(function* (route, message) {
|
|
103
|
+
const content = [];
|
|
104
|
+
for (const part of message.content) {
|
|
105
|
+
if (!supportsContent(part, ["text"]))
|
|
106
|
+
return yield* unsupportedContent(route, "system", ["text"]);
|
|
107
|
+
content.push(part);
|
|
108
|
+
}
|
|
109
|
+
return content;
|
|
110
|
+
});
|
|
111
|
+
/** Lower an unsupported privileged update into visible, in-order user text. */
|
|
112
|
+
export const wrappedSystemUpdate = Effect.fn("ProviderShared.wrappedSystemUpdate")(function* (route, message) {
|
|
113
|
+
const content = yield* systemUpdateText(route, message);
|
|
114
|
+
return { type: "text", text: wrapSystemUpdate(content), cache: content.at(-1)?.cache };
|
|
115
|
+
});
|
|
116
|
+
/**
|
|
117
|
+
* Parse the streamed JSON input of a tool call. Treats an empty string as
|
|
118
|
+
* `"{}"` — providers occasionally finish a tool call without ever emitting
|
|
119
|
+
* input deltas (e.g. zero-arg tools). The error message is uniform across
|
|
120
|
+
* routes: `Invalid JSON input for <route> tool call <name>`.
|
|
121
|
+
*/
|
|
122
|
+
export const parseToolInput = (route, name, raw) => parseJson(route, raw || "{}", `Invalid JSON input for ${route} tool call ${name}`);
|
|
123
|
+
export const normalizeMedia = (part) => {
|
|
124
|
+
const mime = part.mediaType.toLowerCase();
|
|
125
|
+
if (typeof part.data !== "string") {
|
|
126
|
+
const base64 = Buffer.from(part.data).toString("base64");
|
|
127
|
+
return { mime, base64, dataUrl: `data:${mime};base64,${base64}` };
|
|
128
|
+
}
|
|
129
|
+
if (!part.data.startsWith("data:"))
|
|
130
|
+
return { mime, base64: part.data, dataUrl: `data:${mime};base64,${part.data}` };
|
|
131
|
+
return { mime, base64: part.data.slice(part.data.indexOf(",") + 1), dataUrl: part.data };
|
|
132
|
+
};
|
|
133
|
+
export const normalizeToolFile = (part) => normalizeMedia({ type: "media", mediaType: part.mime, data: part.uri, filename: part.name });
|
|
134
|
+
export const trimBaseUrl = (value) => value.replace(/\/+$/, "");
|
|
135
|
+
export const toolResultText = (part) => {
|
|
136
|
+
if (part.result.type === "text")
|
|
137
|
+
return String(part.result.value);
|
|
138
|
+
if (part.result.type === "error") {
|
|
139
|
+
const value = part.result.value;
|
|
140
|
+
const prototype = typeof value === "object" && value !== null && !Array.isArray(value) && Object.getPrototypeOf(value);
|
|
141
|
+
const structured = Array.isArray(value) || prototype === Object.prototype || prototype === null;
|
|
142
|
+
return structured && isJson(value) ? encodeJson(value) : String(value);
|
|
143
|
+
}
|
|
144
|
+
return encodeJson(part.result.value);
|
|
145
|
+
};
|
|
146
|
+
export const errorText = (error) => {
|
|
147
|
+
if (error instanceof Error)
|
|
148
|
+
return error.message;
|
|
149
|
+
if (typeof error === "string")
|
|
150
|
+
return error;
|
|
151
|
+
if (typeof error === "number" || typeof error === "boolean" || typeof error === "bigint")
|
|
152
|
+
return String(error);
|
|
153
|
+
if (error === null)
|
|
154
|
+
return "null";
|
|
155
|
+
if (error === undefined)
|
|
156
|
+
return "undefined";
|
|
157
|
+
return "Unknown stream error";
|
|
158
|
+
};
|
|
159
|
+
/**
|
|
160
|
+
* `framing` step for Server-Sent Events. Decodes UTF-8, runs the SSE channel
|
|
161
|
+
* decoder, optionally filters named events, and drops empty events. `[DONE]`
|
|
162
|
+
* is dropped by default or retained for protocols that use it as their stream
|
|
163
|
+
* boundary. Retry control events are ignored without interrupting the stream.
|
|
164
|
+
* Decoder failures become provider output errors so the public error channel
|
|
165
|
+
* stays `AIError`.
|
|
166
|
+
*/
|
|
167
|
+
export const sseFraming = (bytes, events, includeDone = false) => bytes.pipe(Stream.decodeText(), Stream.mapAccumEffect(() => {
|
|
168
|
+
const output = [];
|
|
169
|
+
return {
|
|
170
|
+
output,
|
|
171
|
+
parser: Sse.makeParser((event) => {
|
|
172
|
+
if (event._tag === "Event")
|
|
173
|
+
output.push(event);
|
|
174
|
+
}),
|
|
175
|
+
};
|
|
176
|
+
}, (state, chunk) => Effect.gen(function* () {
|
|
177
|
+
const error = state.parser.feed(chunk);
|
|
178
|
+
if (error)
|
|
179
|
+
return yield* eventError("sse", error.message, chunk, error);
|
|
180
|
+
return [state, state.output.splice(0)];
|
|
181
|
+
})), Stream.filter((event) => (events === undefined || events.has(event.event)) &&
|
|
182
|
+
event.data.length > 0 &&
|
|
183
|
+
(event.data !== "[DONE]" || includeDone || (events !== undefined && event.event !== "message"))), Stream.map((event) => event.data));
|
|
184
|
+
/**
|
|
185
|
+
* Canonical invalid-request constructor shared by protocol lowering.
|
|
186
|
+
*/
|
|
187
|
+
export const invalidRequest = (message, cause) => new AIError({
|
|
188
|
+
reason: new InvalidRequestError({ message, cause }),
|
|
189
|
+
});
|
|
190
|
+
/**
|
|
191
|
+
* Canonical constructor for operations the selected route does not implement.
|
|
192
|
+
* Prefer this over `invalidRequest` when the failure is a missing route
|
|
193
|
+
* capability rather than a malformed caller input, so consumers can branch on
|
|
194
|
+
* `reason._tag` plus `reason.operation` instead of matching message text.
|
|
195
|
+
*/
|
|
196
|
+
export const unsupportedOperation = (input) => new AIError({
|
|
197
|
+
reason: new UnsupportedOperationError({
|
|
198
|
+
operation: input.operation,
|
|
199
|
+
message: input.message,
|
|
200
|
+
provider: input.provider,
|
|
201
|
+
route: input.route,
|
|
202
|
+
cause: input.cause,
|
|
203
|
+
}),
|
|
204
|
+
});
|
|
205
|
+
/**
|
|
206
|
+
* Lower namespaces to flat definitions for protocols without a native
|
|
207
|
+
* namespace construct. Leaf names join their namespace path with `_` because
|
|
208
|
+
* `.` is not broadly accepted in provider tool names.
|
|
209
|
+
*/
|
|
210
|
+
export const flattenTools = (tools, path = []) => {
|
|
211
|
+
const flat = tools.flatMap((tool) => {
|
|
212
|
+
if (tool.type === "namespace")
|
|
213
|
+
return flattenTools(tool.tools, [...path, tool.name]);
|
|
214
|
+
if (path.length === 0)
|
|
215
|
+
return [tool];
|
|
216
|
+
return [new ToolDefinition({ ...tool, name: [...path, tool.name].join("_") })];
|
|
217
|
+
});
|
|
218
|
+
return [...new Map(flat.map((tool) => [tool.name, tool])).values()];
|
|
219
|
+
};
|
|
220
|
+
export const flattenToolRequest = (request) => {
|
|
221
|
+
const messages = request.messages.map((message) => {
|
|
222
|
+
const content = message.content.map((part) => {
|
|
223
|
+
if ((part.type !== "tool-call" && part.type !== "tool-result") || part.namespace === undefined)
|
|
224
|
+
return part;
|
|
225
|
+
return { ...part, name: `${part.namespace}_${part.name}`, namespace: undefined };
|
|
226
|
+
});
|
|
227
|
+
return content.every((part, index) => part === message.content[index])
|
|
228
|
+
? message
|
|
229
|
+
: new Message({ ...message, content });
|
|
230
|
+
});
|
|
231
|
+
return {
|
|
232
|
+
tools: flattenTools(request.tools),
|
|
233
|
+
request: messages.every((message, index) => message === request.messages[index])
|
|
234
|
+
? request
|
|
235
|
+
: LLMRequest.update(request, { messages }),
|
|
236
|
+
};
|
|
237
|
+
};
|
|
238
|
+
export const imageResponse = Effect.fn("ProviderShared.imageResponse")(function* (route, name, response) {
|
|
239
|
+
const http = new HttpContext({ url: response.request.url, status: response.status, headers: response.headers });
|
|
240
|
+
const body = yield* response.text.pipe(Effect.mapError((cause) => new AIError({
|
|
241
|
+
reason: new InvalidProviderOutputError({
|
|
242
|
+
route,
|
|
243
|
+
message: `Failed to read the ${name} response`,
|
|
244
|
+
http,
|
|
245
|
+
cause,
|
|
246
|
+
}),
|
|
247
|
+
})));
|
|
248
|
+
return {
|
|
249
|
+
body,
|
|
250
|
+
invalid: (message, cause) => new AIError({
|
|
251
|
+
reason: new InvalidProviderOutputError({ route, message, body, http, cause }),
|
|
252
|
+
}),
|
|
253
|
+
};
|
|
254
|
+
});
|
|
255
|
+
export const matchToolChoice = (route, toolChoice, cases) => Effect.gen(function* () {
|
|
256
|
+
if (toolChoice.type === "auto")
|
|
257
|
+
return cases.auto();
|
|
258
|
+
if (toolChoice.type === "none")
|
|
259
|
+
return cases.none();
|
|
260
|
+
if (toolChoice.type === "required")
|
|
261
|
+
return cases.required();
|
|
262
|
+
if (!toolChoice.name)
|
|
263
|
+
return yield* invalidRequest(`${route} tool choice requires a tool name`);
|
|
264
|
+
return cases.tool(toolChoice.name);
|
|
265
|
+
});
|
|
266
|
+
const formatContentTypes = (types) => {
|
|
267
|
+
if (types.length <= 1)
|
|
268
|
+
return types[0] ?? "";
|
|
269
|
+
if (types.length === 2)
|
|
270
|
+
return `${types[0]} and ${types[1]}`;
|
|
271
|
+
return `${types.slice(0, -1).join(", ")}, and ${types.at(-1)}`;
|
|
272
|
+
};
|
|
273
|
+
export const supportsContent = (part, types) => types.includes(part.type);
|
|
274
|
+
export const unsupportedContent = (route, role, types) => invalidRequest(`${route} ${role} messages only support ${formatContentTypes(types)} content for now`);
|
|
275
|
+
/**
|
|
276
|
+
* Build a `validate` step from a Schema decoder. Replaces the per-route
|
|
277
|
+
* lambda body `(payload) => decode(payload).pipe(Effect.mapError((e) =>
|
|
278
|
+
* invalid(e.message)))`. Any decode error is translated into
|
|
279
|
+
* `AIError` carrying the original parse-error message.
|
|
280
|
+
*/
|
|
281
|
+
export const validateWith = (decode) => (payload) => decode(payload).pipe(Effect.mapError((error) => invalidRequest(error.message, error)));
|
|
282
|
+
/**
|
|
283
|
+
* Build an HTTP POST with a JSON body. Sets `content-type: application/json`
|
|
284
|
+
* automatically after caller-supplied headers so routes cannot accidentally
|
|
285
|
+
* send JSON with a stale content type. The body is passed pre-encoded so
|
|
286
|
+
* routes can choose between
|
|
287
|
+
* `Schema.encodeSync(payload)` and `ProviderShared.encodeJson(payload)`.
|
|
288
|
+
*/
|
|
289
|
+
export const jsonPost = (input) => HttpClientRequest.post(input.url).pipe(HttpClientRequest.setHeaders(Headers.set(Headers.fromInput(input.headers), "content-type", "application/json")), HttpClientRequest.bodyText(input.body, "application/json"));
|
|
290
|
+
export * as ProviderShared from "./shared.js";
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { Effect } from "effect";
|
|
2
|
+
import { Auth } from "../../route/auth.js";
|
|
3
|
+
import { AIError } from "../../schema/index.js";
|
|
4
|
+
/**
|
|
5
|
+
* AWS credentials for SigV4 signing. Bedrock also supports Bearer API key auth,
|
|
6
|
+
* which provider facades configure as route auth instead of SigV4.
|
|
7
|
+
*/
|
|
8
|
+
export interface Credentials {
|
|
9
|
+
readonly region: string;
|
|
10
|
+
readonly accessKeyId: string;
|
|
11
|
+
readonly secretAccessKey: string;
|
|
12
|
+
readonly sessionToken?: string;
|
|
13
|
+
}
|
|
14
|
+
/** Static credentials or an effect resolved before every request. */
|
|
15
|
+
export type CredentialSource = Credentials | Effect.Effect<Credentials, AIError>;
|
|
16
|
+
export interface DefaultChainOptions {
|
|
17
|
+
readonly region: string;
|
|
18
|
+
/** Shared config profile passed to the AWS default chain. */
|
|
19
|
+
readonly profile?: string;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Resolve credentials through the AWS default provider chain: environment
|
|
23
|
+
* variables, shared config and SSO caches, web identity tokens, process
|
|
24
|
+
* credentials, and container or instance metadata. A fresh chain runs on every
|
|
25
|
+
* request so credentials rotated on disk without an expiration (for example
|
|
26
|
+
* shared-config keys rewritten by a corporate SSO tool) are always re-read;
|
|
27
|
+
* the SDK's own memoization would otherwise pin them for the process lifetime.
|
|
28
|
+
*/
|
|
29
|
+
export declare const defaultChain: (options: DefaultChainOptions) => Effect.Effect<Credentials, AIError>;
|
|
30
|
+
/** Sign the exact JSON bytes with SigV4 using credentials configured on the route. */
|
|
31
|
+
export declare const sigV4: (source: CredentialSource | undefined, options?: {
|
|
32
|
+
readonly service?: string;
|
|
33
|
+
readonly name?: string;
|
|
34
|
+
}) => Auth.Definition;
|
|
35
|
+
/** Bedrock route auth defaults to SigV4 and expects credentials from route configuration. */
|
|
36
|
+
export declare const auth: Auth.Definition;
|
|
37
|
+
export declare const resolveRegion: (input: {
|
|
38
|
+
readonly region?: string;
|
|
39
|
+
readonly credentials?: {
|
|
40
|
+
readonly region: string;
|
|
41
|
+
};
|
|
42
|
+
}) => string;
|
|
43
|
+
export interface ResolveAuthInput {
|
|
44
|
+
readonly apiKey?: string;
|
|
45
|
+
readonly credentials?: Credentials;
|
|
46
|
+
readonly profile?: string;
|
|
47
|
+
}
|
|
48
|
+
export interface ResolveAuthOptions {
|
|
49
|
+
readonly service?: string;
|
|
50
|
+
readonly name?: string;
|
|
51
|
+
/** `sigv4` ignores an ambient `AWS_BEARER_TOKEN_BEDROCK`; `bearer` is validated by the caller. */
|
|
52
|
+
readonly mode?: "bearer" | "sigv4";
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Bearer wins over SigV4 and explicit static credentials win over the default
|
|
56
|
+
* chain, matching the AWS SDK's own precedence for `AWS_BEARER_TOKEN_BEDROCK`.
|
|
57
|
+
* The region is applied to the SigV4 scope so it always matches the endpoint host.
|
|
58
|
+
*/
|
|
59
|
+
export declare const resolveAuth: (input: ResolveAuthInput, region: string, options?: ResolveAuthOptions) => Auth.Definition;
|
|
60
|
+
export * as BedrockAuth from "./bedrock-auth.js";
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import { AwsV4Signer } from "aws4fetch";
|
|
2
|
+
import { Effect } from "effect";
|
|
3
|
+
import { Headers } from "effect/unstable/http";
|
|
4
|
+
import { Auth } from "../../route/auth.js";
|
|
5
|
+
import { AIError, AuthenticationError } from "../../schema/index.js";
|
|
6
|
+
import { ProviderShared } from "../shared.js";
|
|
7
|
+
/**
|
|
8
|
+
* Resolve credentials through the AWS default provider chain: environment
|
|
9
|
+
* variables, shared config and SSO caches, web identity tokens, process
|
|
10
|
+
* credentials, and container or instance metadata. A fresh chain runs on every
|
|
11
|
+
* request so credentials rotated on disk without an expiration (for example
|
|
12
|
+
* shared-config keys rewritten by a corporate SSO tool) are always re-read;
|
|
13
|
+
* the SDK's own memoization would otherwise pin them for the process lifetime.
|
|
14
|
+
*/
|
|
15
|
+
export const defaultChain = (options) => Effect.tryPromise({
|
|
16
|
+
try: async () => {
|
|
17
|
+
const { fromNodeProviderChain } = await import("@aws-sdk/credential-providers");
|
|
18
|
+
const identity = await fromNodeProviderChain(options.profile === undefined ? {} : { profile: options.profile })();
|
|
19
|
+
return {
|
|
20
|
+
region: options.region,
|
|
21
|
+
accessKeyId: identity.accessKeyId,
|
|
22
|
+
secretAccessKey: identity.secretAccessKey,
|
|
23
|
+
...(identity.sessionToken === undefined ? {} : { sessionToken: identity.sessionToken }),
|
|
24
|
+
};
|
|
25
|
+
},
|
|
26
|
+
catch: (error) => new AIError({
|
|
27
|
+
reason: new AuthenticationError({
|
|
28
|
+
message: `AWS default credential chain failed: ${ProviderShared.errorText(error)}`,
|
|
29
|
+
cause: error,
|
|
30
|
+
}),
|
|
31
|
+
}),
|
|
32
|
+
});
|
|
33
|
+
const signRequest = (input) => Effect.tryPromise({
|
|
34
|
+
try: async () => {
|
|
35
|
+
const signed = await new AwsV4Signer({
|
|
36
|
+
url: input.url,
|
|
37
|
+
method: "POST",
|
|
38
|
+
headers: Object.entries(input.headers),
|
|
39
|
+
body: input.body,
|
|
40
|
+
region: input.credentials.region,
|
|
41
|
+
accessKeyId: input.credentials.accessKeyId,
|
|
42
|
+
secretAccessKey: input.credentials.secretAccessKey,
|
|
43
|
+
sessionToken: input.credentials.sessionToken,
|
|
44
|
+
service: input.service,
|
|
45
|
+
}).sign();
|
|
46
|
+
return Object.fromEntries(signed.headers.entries());
|
|
47
|
+
},
|
|
48
|
+
catch: (error) => ProviderShared.invalidRequest(`${input.name} SigV4 signing failed: ${error instanceof Error ? error.message : String(error)}`),
|
|
49
|
+
});
|
|
50
|
+
/** Sign the exact JSON bytes with SigV4 using credentials configured on the route. */
|
|
51
|
+
export const sigV4 = (source, options = {}) => Auth.custom((input) => {
|
|
52
|
+
return Effect.gen(function* () {
|
|
53
|
+
if (!source) {
|
|
54
|
+
return yield* ProviderShared.invalidRequest(`${options.name ?? "Bedrock Converse"} requires either route bearer auth or AWS credentials configured on the route`);
|
|
55
|
+
}
|
|
56
|
+
const credentials = Effect.isEffect(source) ? yield* source : source;
|
|
57
|
+
const headersForSigning = Headers.set(input.headers, "content-type", "application/json");
|
|
58
|
+
const signed = yield* signRequest({
|
|
59
|
+
url: input.url,
|
|
60
|
+
body: input.body,
|
|
61
|
+
headers: headersForSigning,
|
|
62
|
+
credentials,
|
|
63
|
+
service: options.service ?? "bedrock",
|
|
64
|
+
name: options.name ?? "Bedrock Converse",
|
|
65
|
+
});
|
|
66
|
+
return Headers.setAll(headersForSigning, signed);
|
|
67
|
+
});
|
|
68
|
+
});
|
|
69
|
+
/** Bedrock route auth defaults to SigV4 and expects credentials from route configuration. */
|
|
70
|
+
export const auth = sigV4(undefined);
|
|
71
|
+
export const resolveRegion = (input) => input.region ?? input.credentials?.region ?? process.env.AWS_REGION ?? process.env.AWS_DEFAULT_REGION ?? "us-east-1";
|
|
72
|
+
/**
|
|
73
|
+
* Bearer wins over SigV4 and explicit static credentials win over the default
|
|
74
|
+
* chain, matching the AWS SDK's own precedence for `AWS_BEARER_TOKEN_BEDROCK`.
|
|
75
|
+
* The region is applied to the SigV4 scope so it always matches the endpoint host.
|
|
76
|
+
*/
|
|
77
|
+
export const resolveAuth = (input, region, options = {}) => {
|
|
78
|
+
const apiKey = options.mode === "sigv4" ? undefined : (input.apiKey ?? process.env.AWS_BEARER_TOKEN_BEDROCK);
|
|
79
|
+
if (apiKey !== undefined)
|
|
80
|
+
return Auth.bearer(apiKey);
|
|
81
|
+
if (input.credentials !== undefined)
|
|
82
|
+
return sigV4({ ...input.credentials, region }, options);
|
|
83
|
+
return sigV4(defaultChain({ region, profile: input.profile }), options);
|
|
84
|
+
};
|
|
85
|
+
export * as BedrockAuth from "./bedrock-auth.js";
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { Schema } from "effect";
|
|
2
|
+
import type { CacheHint } from "../../schema/index.js";
|
|
3
|
+
export declare const CachePointBlock: Schema.Struct<{
|
|
4
|
+
readonly cachePoint: Schema.Struct<{
|
|
5
|
+
readonly type: Schema.tag<"default">;
|
|
6
|
+
readonly ttl: Schema.optional<Schema.Literals<readonly ["5m", "1h"]>>;
|
|
7
|
+
}>;
|
|
8
|
+
}>;
|
|
9
|
+
export type CachePointBlock = Schema.Schema.Type<typeof CachePointBlock>;
|
|
10
|
+
export declare const BEDROCK_BREAKPOINT_CAP = 4;
|
|
11
|
+
export declare const breakpoints: (modelID: string) => {
|
|
12
|
+
supported: boolean;
|
|
13
|
+
ttl1h: boolean;
|
|
14
|
+
remaining: number;
|
|
15
|
+
dropped: number;
|
|
16
|
+
};
|
|
17
|
+
export type Breakpoints = ReturnType<typeof breakpoints>;
|
|
18
|
+
export declare const block: (breakpoints: Breakpoints, cache: CacheHint | undefined) => CachePointBlock | undefined;
|
|
19
|
+
export * as BedrockCache from "./bedrock-cache.js";
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { Schema } from "effect";
|
|
2
|
+
import { newBreakpoints, ttlBucket } from "./cache.js";
|
|
3
|
+
// Bedrock cache markers are positional: emit a `cachePoint` block immediately
|
|
4
|
+
// after the content the caller wants treated as a cacheable prefix. Bedrock
|
|
5
|
+
// accepts optional `ttl: "5m" | "1h"` on cachePoint.
|
|
6
|
+
export const CachePointBlock = Schema.Struct({
|
|
7
|
+
cachePoint: Schema.Struct({
|
|
8
|
+
type: Schema.tag("default"),
|
|
9
|
+
ttl: Schema.optional(Schema.Literals(["5m", "1h"])),
|
|
10
|
+
}),
|
|
11
|
+
});
|
|
12
|
+
const LEGACY_CLAUDE = ["anthropic.claude-instant", "anthropic.claude-v1", "anthropic.claude-v2", "anthropic.claude-3-"];
|
|
13
|
+
// These legacy Claude releases support explicit caching, but only for five minutes.
|
|
14
|
+
const CLAUDE_5M = [
|
|
15
|
+
"anthropic.claude-3-5-sonnet-20241022-v2:0",
|
|
16
|
+
"anthropic.claude-3-5-haiku-20241022-v1:0",
|
|
17
|
+
"anthropic.claude-3-7-sonnet-20250219-v1:0",
|
|
18
|
+
"anthropic.claude-sonnet-4-20250514-v1:0",
|
|
19
|
+
"anthropic.claude-opus-4-20250514-v1:0",
|
|
20
|
+
"anthropic.claude-opus-4-1-20250805-v1:0",
|
|
21
|
+
];
|
|
22
|
+
// Callers share the four-breakpoint budget across system, messages, and tools.
|
|
23
|
+
export const BEDROCK_BREAKPOINT_CAP = 4;
|
|
24
|
+
export const breakpoints = (modelID) => {
|
|
25
|
+
// Substring matching also handles regional prefixes and model-bearing ARNs.
|
|
26
|
+
const short = CLAUDE_5M.some((id) => modelID.includes(id));
|
|
27
|
+
return {
|
|
28
|
+
...newBreakpoints(BEDROCK_BREAKPOINT_CAP),
|
|
29
|
+
// Assume modern Claude releases retain caching support; older generations need an explicit exception.
|
|
30
|
+
// Other model families use implicit caching where available.
|
|
31
|
+
supported: modelID.includes("anthropic.claude-") && (short || !LEGACY_CLAUDE.some((id) => modelID.includes(id))),
|
|
32
|
+
ttl1h: !short,
|
|
33
|
+
};
|
|
34
|
+
};
|
|
35
|
+
const DEFAULT_5M = { cachePoint: { type: "default" } };
|
|
36
|
+
const DEFAULT_1H = { cachePoint: { type: "default", ttl: "1h" } };
|
|
37
|
+
export const block = (breakpoints, cache) => {
|
|
38
|
+
if (!breakpoints.supported)
|
|
39
|
+
return undefined;
|
|
40
|
+
if (cache?.type !== "ephemeral" && cache?.type !== "persistent")
|
|
41
|
+
return undefined;
|
|
42
|
+
if (breakpoints.remaining <= 0) {
|
|
43
|
+
breakpoints.dropped += 1;
|
|
44
|
+
return undefined;
|
|
45
|
+
}
|
|
46
|
+
breakpoints.remaining -= 1;
|
|
47
|
+
return breakpoints.ttl1h && ttlBucket(cache.ttlSeconds) === "1h" ? DEFAULT_1H : DEFAULT_5M;
|
|
48
|
+
};
|
|
49
|
+
export * as BedrockCache from "./bedrock-cache.js";
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { Effect, Schema } from "effect";
|
|
2
|
+
export declare const ImageFormat: Schema.Literals<readonly ["png", "jpeg", "gif", "webp"]>;
|
|
3
|
+
export type ImageFormat = Schema.Schema.Type<typeof ImageFormat>;
|
|
4
|
+
export declare const ImageBlock: Schema.Struct<{
|
|
5
|
+
readonly image: Schema.Struct<{
|
|
6
|
+
readonly format: Schema.Literals<readonly ["png", "jpeg", "gif", "webp"]>;
|
|
7
|
+
readonly source: Schema.Struct<{
|
|
8
|
+
readonly bytes: Schema.String;
|
|
9
|
+
}>;
|
|
10
|
+
}>;
|
|
11
|
+
}>;
|
|
12
|
+
export type ImageBlock = Schema.Schema.Type<typeof ImageBlock>;
|
|
13
|
+
export declare const DocumentFormat: Schema.Literals<readonly ["pdf", "csv", "doc", "docx", "xls", "xlsx", "html", "txt", "md"]>;
|
|
14
|
+
export type DocumentFormat = Schema.Schema.Type<typeof DocumentFormat>;
|
|
15
|
+
export declare const DocumentBlock: Schema.Struct<{
|
|
16
|
+
readonly document: Schema.Struct<{
|
|
17
|
+
readonly format: Schema.Literals<readonly ["pdf", "csv", "doc", "docx", "xls", "xlsx", "html", "txt", "md"]>;
|
|
18
|
+
readonly name: Schema.String;
|
|
19
|
+
readonly source: Schema.Struct<{
|
|
20
|
+
readonly bytes: Schema.String;
|
|
21
|
+
}>;
|
|
22
|
+
}>;
|
|
23
|
+
}>;
|
|
24
|
+
export type DocumentBlock = Schema.Schema.Type<typeof DocumentBlock>;
|
|
25
|
+
export declare const lower: (part: {
|
|
26
|
+
readonly data: string | Uint8Array<ArrayBufferLike>;
|
|
27
|
+
readonly type: "media";
|
|
28
|
+
readonly mediaType: string;
|
|
29
|
+
readonly metadata?: {
|
|
30
|
+
readonly [x: string]: unknown;
|
|
31
|
+
} | undefined;
|
|
32
|
+
readonly cache?: import("../../schema/options.js").CacheHint | undefined;
|
|
33
|
+
readonly providerMetadata?: {
|
|
34
|
+
readonly [x: string]: {
|
|
35
|
+
readonly [x: string]: unknown;
|
|
36
|
+
};
|
|
37
|
+
} | undefined;
|
|
38
|
+
readonly filename?: string | undefined;
|
|
39
|
+
}) => Effect.Effect<{
|
|
40
|
+
readonly document: {
|
|
41
|
+
readonly format: "pdf" | "csv" | "doc" | "docx" | "xls" | "xlsx" | "html" | "txt" | "md";
|
|
42
|
+
readonly name: string;
|
|
43
|
+
readonly source: {
|
|
44
|
+
readonly bytes: string;
|
|
45
|
+
};
|
|
46
|
+
};
|
|
47
|
+
} | {
|
|
48
|
+
image: {
|
|
49
|
+
format: "png" | "jpeg" | "gif" | "webp";
|
|
50
|
+
source: {
|
|
51
|
+
bytes: string;
|
|
52
|
+
};
|
|
53
|
+
};
|
|
54
|
+
}, import("../../schema/errors.js").AIError, never>;
|
|
55
|
+
export * as BedrockMedia from "./bedrock-media.js";
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import { Effect, Encoding, Schema } from "effect";
|
|
2
|
+
import { ProviderShared } from "../shared.js";
|
|
3
|
+
// Bedrock Converse accepts image `format` as the file extension and
|
|
4
|
+
// `source.bytes` as base64 in the JSON wire format.
|
|
5
|
+
export const ImageFormat = Schema.Literals(["png", "jpeg", "gif", "webp"]);
|
|
6
|
+
export const ImageBlock = Schema.Struct({
|
|
7
|
+
image: Schema.Struct({
|
|
8
|
+
format: ImageFormat,
|
|
9
|
+
source: Schema.Struct({ bytes: Schema.String }),
|
|
10
|
+
}),
|
|
11
|
+
});
|
|
12
|
+
// Bedrock document blocks require a user-facing name so the model can refer to
|
|
13
|
+
// the uploaded document.
|
|
14
|
+
export const DocumentFormat = Schema.Literals(["pdf", "csv", "doc", "docx", "xls", "xlsx", "html", "txt", "md"]);
|
|
15
|
+
export const DocumentBlock = Schema.Struct({
|
|
16
|
+
document: Schema.Struct({
|
|
17
|
+
format: DocumentFormat,
|
|
18
|
+
name: Schema.String,
|
|
19
|
+
source: Schema.Struct({ bytes: Schema.String }),
|
|
20
|
+
}),
|
|
21
|
+
});
|
|
22
|
+
const IMAGE_FORMATS = {
|
|
23
|
+
"image/png": "png",
|
|
24
|
+
"image/jpeg": "jpeg",
|
|
25
|
+
"image/jpg": "jpeg",
|
|
26
|
+
"image/gif": "gif",
|
|
27
|
+
"image/webp": "webp",
|
|
28
|
+
};
|
|
29
|
+
const DOCUMENT_FORMATS = {
|
|
30
|
+
"application/pdf": "pdf",
|
|
31
|
+
"text/csv": "csv",
|
|
32
|
+
"application/msword": "doc",
|
|
33
|
+
"application/vnd.openxmlformats-officedocument.wordprocessingml.document": "docx",
|
|
34
|
+
"application/vnd.ms-excel": "xls",
|
|
35
|
+
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet": "xlsx",
|
|
36
|
+
"text/html": "html",
|
|
37
|
+
"text/plain": "txt",
|
|
38
|
+
"text/markdown": "md",
|
|
39
|
+
};
|
|
40
|
+
const documentBlock = (name, format, bytes) => ({
|
|
41
|
+
document: {
|
|
42
|
+
format,
|
|
43
|
+
name,
|
|
44
|
+
source: { bytes },
|
|
45
|
+
},
|
|
46
|
+
});
|
|
47
|
+
const mediaBase64 = Effect.fn("BedrockMedia.mediaBase64")(function* (part) {
|
|
48
|
+
const media = ProviderShared.normalizeMedia(part);
|
|
49
|
+
const bytes = yield* Effect.fromResult(Encoding.decodeBase64(media.base64)).pipe(Effect.mapError((cause) => ProviderShared.invalidRequest("Bedrock Converse media data must be valid base64", cause)));
|
|
50
|
+
return Encoding.encodeBase64(bytes);
|
|
51
|
+
});
|
|
52
|
+
// Route by MIME. Known image/document formats lower into a typed block; anything
|
|
53
|
+
// else fails with a clear error instead of silently degrading to a malformed
|
|
54
|
+
// document block. Image MIME types not in `IMAGE_FORMATS` (e.g. `image/svg+xml`)
|
|
55
|
+
// get an image-specific error so the caller knows it's a format-support issue,
|
|
56
|
+
// not a kind-detection issue.
|
|
57
|
+
export const lower = Effect.fn("BedrockMedia.lower")(function* (part) {
|
|
58
|
+
const mime = part.mediaType.toLowerCase();
|
|
59
|
+
const imageFormat = IMAGE_FORMATS[mime];
|
|
60
|
+
if (imageFormat) {
|
|
61
|
+
return { image: { format: imageFormat, source: { bytes: yield* mediaBase64(part) } } };
|
|
62
|
+
}
|
|
63
|
+
if (mime.startsWith("image/"))
|
|
64
|
+
return yield* ProviderShared.invalidRequest(`Bedrock Converse does not support image media type ${part.mediaType}`);
|
|
65
|
+
const documentFormat = DOCUMENT_FORMATS[mime];
|
|
66
|
+
if (documentFormat) {
|
|
67
|
+
if (!part.filename)
|
|
68
|
+
return yield* ProviderShared.invalidRequest("Bedrock Converse document media requires a filename");
|
|
69
|
+
return documentBlock(part.filename, documentFormat, yield* mediaBase64(part));
|
|
70
|
+
}
|
|
71
|
+
return yield* ProviderShared.invalidRequest(`Bedrock Converse does not support media type ${part.mediaType}`);
|
|
72
|
+
});
|
|
73
|
+
export * as BedrockMedia from "./bedrock-media.js";
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
// Shared counter and TTL mapping for provider cache-marker lowering.
|
|
2
|
+
export const newBreakpoints = (cap) => ({ remaining: cap, dropped: 0 });
|
|
3
|
+
// Requests of at least one hour use the explicit `"1h"` bucket; shorter
|
|
4
|
+
// requests omit the wire TTL and use the provider default.
|
|
5
|
+
export const ttlBucket = (ttlSeconds) => ttlSeconds !== undefined && ttlSeconds >= 3600 ? "1h" : undefined;
|