opencode-crs-bedrock 1.0.1 → 1.0.3

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 CHANGED
@@ -45,7 +45,11 @@ npm install -g opencode-crs-bedrock
45
45
 
46
46
  ## Configuration
47
47
 
48
- ### 1. Set Environment Variables
48
+ > **Note**: Both `apiKey` and `baseURL` are optional. You can configure them via environment variables, use `opencode auth login crs` for interactive setup, or configure them directly in the config file.
49
+
50
+ ### Option 1: Using Environment Variables (Recommended)
51
+
52
+ Set environment variables for automatic configuration:
49
53
 
50
54
  ```bash
51
55
  export ANTHROPIC_BASE_URL=https://your-crs-endpoint.com/api
@@ -54,9 +58,7 @@ export ANTHROPIC_AUTH_TOKEN=cr_your_api_key_here
54
58
 
55
59
  Add to your shell profile (`~/.bashrc`, `~/.zshrc`) to persist.
56
60
 
57
- ### 2. Create OpenCode Config
58
-
59
- Create `opencode.json` in your project or `~/.config/opencode/opencode.json` globally:
61
+ Then create `opencode.json` in your project or `~/.config/opencode/opencode.json`:
60
62
 
61
63
  ```json
62
64
  {
@@ -95,7 +97,58 @@ Create `opencode.json` in your project or `~/.config/opencode/opencode.json` glo
95
97
  }
96
98
  ```
97
99
 
98
- ### 3. Use the Model
100
+ ### Option 2: Using OpenCode Auth (Interactive)
101
+
102
+ The plugin supports interactive authentication. First, create the config **without** `baseURL` or `apiKey`:
103
+
104
+ ```json
105
+ {
106
+ "$schema": "https://opencode.ai/config.json",
107
+ "plugin": [
108
+ "opencode-crs-bedrock"
109
+ ],
110
+ "provider": {
111
+ "crs": {
112
+ "npm": "@ai-sdk/anthropic",
113
+ "models": {
114
+ "claude-sonnet-4-5": {
115
+ "name": "Sonnet 4.5 [CRS]",
116
+ "modalities": { "input": ["text", "image", "pdf"], "output": ["text"] }
117
+ }
118
+ }
119
+ }
120
+ }
121
+ }
122
+ ```
123
+
124
+ Then run the auth login command to store your credentials:
125
+
126
+ ```bash
127
+ opencode auth login crs
128
+ ```
129
+
130
+ OpenCode will prompt you to enter your CRS API key, which will be securely stored. The plugin's auth loader will then be triggered automatically to configure the custom fetch handler for CRS/Bedrock compatibility.
131
+
132
+ ### Option 3: Direct Configuration (No Environment Variables)
133
+
134
+ You can also hardcode credentials directly in the config (not recommended for shared configs):
135
+
136
+ ```json
137
+ {
138
+ "provider": {
139
+ "crs": {
140
+ "npm": "@ai-sdk/anthropic",
141
+ "options": {
142
+ "baseURL": "https://your-crs-endpoint.com/api",
143
+ "apiKey": "cr_your_api_key_here"
144
+ },
145
+ "models": { /* ... */ }
146
+ }
147
+ }
148
+ }
149
+ ```
150
+
151
+ ### Using the Model
99
152
 
100
153
  ```bash
101
154
  # Interactive mode
@@ -185,13 +238,16 @@ CRS API keys start with `cr_`. The plugin uses the `x-api-key` header instead of
185
238
 
186
239
  ### Connection Issues
187
240
 
188
- 1. Verify environment variables:
189
- ```bash
190
- echo $ANTHROPIC_BASE_URL
191
- echo $ANTHROPIC_AUTH_TOKEN
192
- ```
241
+ 1. Check your authentication method:
242
+ - **Environment variables**: Verify they're set:
243
+ ```bash
244
+ echo $ANTHROPIC_BASE_URL
245
+ echo $ANTHROPIC_AUTH_TOKEN
246
+ ```
247
+ - **OpenCode auth**: Run `opencode auth list` to see stored credentials
248
+ - **Direct config**: Check your `opencode.json` has correct values
193
249
 
194
- 2. Test the endpoint:
250
+ 2. Test the endpoint (if using environment variables):
195
251
  ```bash
196
252
  curl -H "x-api-key: $ANTHROPIC_AUTH_TOKEN" "$ANTHROPIC_BASE_URL/v1/messages"
197
253
  ```
@@ -201,6 +257,16 @@ CRS API keys start with `cr_`. The plugin uses the `x-api-key` header instead of
201
257
  CRS_DEBUG_SSE=true opencode run -m crs/claude-sonnet-4-5 "test"
202
258
  ```
203
259
 
260
+ ### Missing API Key or Base URL
261
+
262
+ Both `apiKey` and `baseURL` are **optional** in the plugin:
263
+
264
+ - If **not provided via environment variables or config**, use `opencode auth login crs` to store credentials interactively
265
+ - The plugin defaults to `https://crs.tonob.net/api` if no `baseURL` is specified
266
+ - You can mix and match: set `baseURL` in config and use `opencode auth login` for the `apiKey`, or vice versa
267
+
268
+ **Important**: The plugin's custom fetch handler (which fixes CRS/Bedrock compatibility issues) is only activated when using `opencode auth login` or environment variables. If you hardcode credentials directly in the config, the auth loader may not be triggered.
269
+
204
270
  ## License
205
271
 
206
272
  MIT
package/index.ts CHANGED
@@ -12,8 +12,31 @@
12
12
  * - Bedrock model ID mapping to standard Anthropic format
13
13
  */
14
14
 
15
- import type { Plugin } from "@opencode-ai/plugin";
16
- import type { Auth, Provider } from "@opencode-ai/sdk";
15
+ import type { Plugin, PluginInput } from "@opencode-ai/plugin";
16
+ import type { Provider } from "@opencode-ai/sdk";
17
+
18
+ // ============================================================================
19
+ // Type Definitions for Plugin
20
+ // ============================================================================
21
+
22
+ interface AuthDetails {
23
+ type?: string;
24
+ [key: string]: unknown;
25
+ }
26
+
27
+ type GetAuth = () => Promise<AuthDetails>;
28
+
29
+ interface LoaderResult {
30
+ apiKey?: string;
31
+ baseURL?: string;
32
+ fetch: typeof fetch;
33
+ }
34
+
35
+ type PluginClient = PluginInput["client"];
36
+
37
+ interface PluginContext {
38
+ client: PluginClient;
39
+ }
17
40
 
18
41
  // ============================================================================
19
42
  // Type Definitions
@@ -967,25 +990,18 @@ async function transformJsonResponse(response: Response): Promise<Response> {
967
990
  }
968
991
  }
969
992
 
970
- type GetAuthFn = () => Promise<{ key: string } | null>;
971
-
972
993
  /**
973
994
  * Creates a custom fetch function that handles CRS-specific transformations
974
995
  */
975
996
  function createCRSFetch(
976
- getAuth: GetAuthFn
997
+ authKey: string
977
998
  ): typeof fetch {
978
999
  return async function crsFetch(
979
1000
  input: RequestInfo | URL,
980
1001
  init?: RequestInit
981
1002
  ): Promise<Response> {
982
- const auth = await getAuth();
983
- if (!auth?.key) {
984
- return fetch(input, init);
985
- }
986
-
987
1003
  const url = normalizeApiUrl(input);
988
- const headers = buildRequestHeaders(input, init, auth.key);
1004
+ const headers = buildRequestHeaders(input, init, authKey);
989
1005
  const tools = extractToolsFromBody(init?.body);
990
1006
 
991
1007
  const response = await fetch(url, { ...init, headers });
@@ -1011,45 +1027,47 @@ function createCRSFetch(
1011
1027
  /**
1012
1028
  * CRS Auth Plugin for OpenCode
1013
1029
  */
1014
- export const CRSAuthPlugin: Plugin = async () => {
1030
+ export const CRSAuthPlugin: Plugin = async ({ client }: PluginContext) => {
1031
+ debugLog("CRS Bedrock plugin initializing...");
1032
+
1015
1033
  return {
1016
1034
  auth: {
1017
1035
  provider: PROVIDER_ID,
1018
1036
 
1019
- async loader(
1020
- getAuth: () => Promise<Auth>,
1021
- provider: Provider
1022
- ) {
1023
- let baseURL = provider?.options?.baseURL as string | undefined;
1024
- if (baseURL && !baseURL.includes("/v1")) {
1025
- baseURL = baseURL.replace(/\/?$/, "/v1/");
1026
- }
1037
+ loader: async (
1038
+ getAuth: GetAuth,
1039
+ _provider: Provider
1040
+ ): Promise<LoaderResult | Record<string, unknown>> => {
1041
+ debugLog("CRS auth loader called");
1042
+
1043
+ const auth = await getAuth();
1044
+ const apiKey = auth.apiKey as string | undefined;
1045
+ const baseURL = (auth.baseURL as string | undefined) || process.env.ANTHROPIC_BASE_URL || "https://crs.tonob.net/api";
1027
1046
 
1028
- let apiKey = provider?.options?.apiKey as string | undefined;
1029
- if (!apiKey) {
1030
- const auth = await getAuth();
1031
- apiKey = auth?.type === "api" ? auth.key : undefined;
1032
- }
1047
+ debugLog("Auth details:", { hasApiKey: !!apiKey, baseURL });
1033
1048
 
1034
1049
  if (!apiKey) {
1035
- return {};
1050
+ debugLog("No API key found, returning baseURL only");
1051
+ return { baseURL };
1036
1052
  }
1037
1053
 
1054
+ debugLog("Returning full config with custom fetch");
1055
+
1038
1056
  return {
1039
- apiKey: "",
1057
+ apiKey,
1040
1058
  baseURL,
1041
- fetch: createCRSFetch(() => Promise.resolve({ key: apiKey! })),
1059
+ fetch: createCRSFetch(apiKey),
1042
1060
  };
1043
1061
  },
1044
1062
 
1045
1063
  methods: [
1046
1064
  {
1047
1065
  label: "Enter CRS API Key",
1048
- type: "api" as const,
1066
+ type: "api",
1049
1067
  prompts: [
1050
1068
  {
1051
- type: "text" as const,
1052
- message: "Enter your CRS API Key (cr_...)",
1069
+ type: "text",
1070
+ message: "Enter your CRS API Key",
1053
1071
  key: "apiKey",
1054
1072
  },
1055
1073
  ],
@@ -1058,3 +1076,4 @@ export const CRSAuthPlugin: Plugin = async () => {
1058
1076
  },
1059
1077
  };
1060
1078
  };
1079
+
package/package.json CHANGED
@@ -1,9 +1,12 @@
1
1
  {
2
2
  "name": "opencode-crs-bedrock",
3
- "version": "1.0.1",
3
+ "version": "1.0.3",
4
4
  "description": "OpenCode plugin for FeedMob CRS proxy to AWS Bedrock Anthropic models",
5
5
  "type": "module",
6
- "main": "index.ts",
6
+ "module": "index.ts",
7
+ "files": [
8
+ "index.ts"
9
+ ],
7
10
  "keywords": [
8
11
  "opencode",
9
12
  "plugin",
@@ -12,8 +15,11 @@
12
15
  "claude",
13
16
  "anthropic"
14
17
  ],
15
- "license": "MIT",
16
18
  "dependencies": {
17
- "@opencode-ai/plugin": "*"
19
+ "@opencode-ai/sdk": "^1.1.23 ",
20
+ "@opencode-ai/plugin": "^1.1.23"
21
+ },
22
+ "devDependencies": {
23
+ "@types/bun": "latest"
18
24
  }
19
25
  }
package/bun.lock DELETED
@@ -1,19 +0,0 @@
1
- {
2
- "lockfileVersion": 1,
3
- "configVersion": 1,
4
- "workspaces": {
5
- "": {
6
- "name": "opencode-crs-bedrock",
7
- "dependencies": {
8
- "@opencode-ai/plugin": "*",
9
- },
10
- },
11
- },
12
- "packages": {
13
- "@opencode-ai/plugin": ["@opencode-ai/plugin@1.1.23", "", { "dependencies": { "@opencode-ai/sdk": "1.1.23", "zod": "4.1.8" } }, "sha512-O/iLSKOUuzD95UWhj9y/tEuycPEBv36de0suHXXqeYLWZLZ16DAUSKR+YG7rvRjJS0sbn4biVMw+k7XXk/oxiQ=="],
14
-
15
- "@opencode-ai/sdk": ["@opencode-ai/sdk@1.1.23", "", {}, "sha512-YjN9ogzkLol92s+/iARXRop9/5oFIezUkvWVay12u1IM6A/WJs50DeKl3oL0x4a68P1a5tI5gD98dLnk2+AlsA=="],
16
-
17
- "zod": ["zod@4.1.8", "", {}, "sha512-5R1P+WwQqmmMIEACyzSvo4JXHY5WiAFHRMg+zBZKgKS+Q1viRa0C1hmUKtHltoIFKtIdki3pRxkmpP74jnNYHQ=="],
18
- }
19
- }
package/dist/index.js DELETED
@@ -1,547 +0,0 @@
1
- // index.ts
2
- var PROVIDER_ID = "crs";
3
- var VALID_SSE_EVENT_TYPES = new Set([
4
- "message_start",
5
- "content_block_start",
6
- "content_block_delta",
7
- "content_block_stop",
8
- "message_delta",
9
- "message_stop",
10
- "ping",
11
- "error"
12
- ]);
13
- var DEBUG_SSE = process.env.CRS_DEBUG_SSE === "true";
14
- function debugLog(...args) {
15
- if (DEBUG_SSE) {
16
- console.error("[CRS-SSE]", ...args);
17
- }
18
- }
19
- function generateToolUseId() {
20
- const chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
21
- let id = "toolu_01";
22
- for (let i = 0;i < 22; i++) {
23
- id += chars.charAt(Math.floor(Math.random() * chars.length));
24
- }
25
- return id;
26
- }
27
- function emitSSEEvent(controller, eventType, data) {
28
- controller.enqueue(`event: ${eventType}
29
- `);
30
- controller.enqueue(`data: ${JSON.stringify(data)}
31
- `);
32
- controller.enqueue(`
33
- `);
34
- }
35
- function emitEventHeader(controller, eventType) {
36
- controller.enqueue(`event: ${eventType}
37
- `);
38
- }
39
- function buildToolSchemas(tools) {
40
- const schemas = new Map;
41
- for (const tool of tools) {
42
- const params = tool.input_schema?.properties || {};
43
- const required = new Set(tool.input_schema?.required || []);
44
- schemas.set(tool.name, { params, required });
45
- }
46
- return schemas;
47
- }
48
- function inferToolName(jsonStr, toolSchemas) {
49
- try {
50
- const input = JSON.parse(jsonStr);
51
- const inputKeys = new Set(Object.keys(input));
52
- let bestMatch = null;
53
- let bestScore = -1;
54
- for (const [toolName, schema] of toolSchemas) {
55
- const schemaKeys = new Set(Object.keys(schema.params));
56
- let matchCount = 0;
57
- let mismatchCount = 0;
58
- for (const key of inputKeys) {
59
- if (schemaKeys.has(key)) {
60
- matchCount++;
61
- } else {
62
- mismatchCount++;
63
- }
64
- }
65
- let hasAllRequired = true;
66
- for (const req of schema.required) {
67
- if (!inputKeys.has(req)) {
68
- hasAllRequired = false;
69
- break;
70
- }
71
- }
72
- const score = matchCount - mismatchCount + (hasAllRequired ? 10 : 0);
73
- if (score > bestScore) {
74
- bestScore = score;
75
- bestMatch = toolName;
76
- }
77
- }
78
- return bestMatch;
79
- } catch {
80
- return null;
81
- }
82
- }
83
- var BEDROCK_MODEL_MAPPINGS = {
84
- "us.anthropic.claude-sonnet-4-20250514-v1:0": "claude-sonnet-4-20250514",
85
- "us.anthropic.claude-opus-4-20250514-v1:0": "claude-opus-4-20250514",
86
- "us.anthropic.claude-3-5-sonnet-20241022-v2:0": "claude-3-5-sonnet-20241022",
87
- "us.anthropic.claude-3-5-haiku-20241022-v1:0": "claude-3-5-haiku-20241022",
88
- "us.anthropic.claude-3-opus-20240229-v1:0": "claude-3-opus-20240229"
89
- };
90
- function mapBedrockModelId(bedrockModelId) {
91
- if (BEDROCK_MODEL_MAPPINGS[bedrockModelId]) {
92
- return BEDROCK_MODEL_MAPPINGS[bedrockModelId];
93
- }
94
- const match = bedrockModelId.match(/us\.anthropic\.(.+?)-v\d+:\d+$/);
95
- if (match) {
96
- return match[1];
97
- }
98
- return bedrockModelId;
99
- }
100
-
101
- class SSETransformState {
102
- toolSchemas;
103
- buffer;
104
- currentEventType;
105
- startedBlocks;
106
- pendingToolBlocks;
107
- constructor(toolSchemas) {
108
- this.toolSchemas = toolSchemas;
109
- this.buffer = "";
110
- this.currentEventType = null;
111
- this.startedBlocks = new Map;
112
- this.pendingToolBlocks = new Map;
113
- }
114
- trackBlockStart(index, contentBlock) {
115
- const blockType = contentBlock?.type || "text";
116
- if (blockType === "tool_use") {
117
- const toolBlock = contentBlock;
118
- this.startedBlocks.set(index, {
119
- type: "tool_use",
120
- id: toolBlock.id || generateToolUseId(),
121
- name: toolBlock.name || "unknown",
122
- inputBuffer: "",
123
- emitted: true
124
- });
125
- } else {
126
- this.startedBlocks.set(index, { type: "text", emitted: true });
127
- }
128
- }
129
- bufferToolDelta(index, eventType, data, partialJson) {
130
- if (!this.pendingToolBlocks.has(index)) {
131
- this.pendingToolBlocks.set(index, {
132
- toolId: generateToolUseId(),
133
- deltas: [],
134
- inputBuffer: ""
135
- });
136
- }
137
- const pending = this.pendingToolBlocks.get(index);
138
- pending.inputBuffer += partialJson;
139
- pending.deltas.push({ event: eventType, data: { ...data } });
140
- debugLog("Buffering tool delta, accumulated:", pending.inputBuffer);
141
- }
142
- hasPendingToolBlock(index) {
143
- return this.pendingToolBlocks.has(index);
144
- }
145
- flushPendingToolBlocks(controller) {
146
- for (const [blockIndex, pending] of this.pendingToolBlocks) {
147
- const inferredName = inferToolName(pending.inputBuffer, this.toolSchemas) || "unknown";
148
- debugLog("Flushing pending tool block", blockIndex, "with inferred name:", inferredName);
149
- emitSSEEvent(controller, "content_block_start", {
150
- type: "content_block_start",
151
- index: blockIndex,
152
- content_block: {
153
- type: "tool_use",
154
- id: pending.toolId,
155
- name: inferredName,
156
- input: {}
157
- }
158
- });
159
- for (const buffered of pending.deltas) {
160
- emitSSEEvent(controller, buffered.event, {
161
- type: buffered.event,
162
- ...buffered.data
163
- });
164
- }
165
- this.startedBlocks.set(blockIndex, {
166
- type: "tool_use",
167
- id: pending.toolId,
168
- name: inferredName,
169
- inputBuffer: pending.inputBuffer,
170
- emitted: true
171
- });
172
- }
173
- this.pendingToolBlocks.clear();
174
- }
175
- closeAllBlocks(controller) {
176
- for (const [blockIndex] of this.startedBlocks) {
177
- emitSSEEvent(controller, "content_block_stop", {
178
- type: "content_block_stop",
179
- index: blockIndex
180
- });
181
- }
182
- this.startedBlocks.clear();
183
- }
184
- }
185
- function createSSETransformStream(tools = []) {
186
- const state = new SSETransformState(buildToolSchemas(tools));
187
- return new TransformStream({
188
- transform(chunk, controller) {
189
- state.buffer += chunk;
190
- const lines = state.buffer.split(`
191
- `);
192
- state.buffer = lines.pop() || "";
193
- for (const line of lines) {
194
- const result = processSSELine(line, state, controller);
195
- if (result === "skip")
196
- continue;
197
- }
198
- },
199
- flush(controller) {
200
- if (state.buffer.trim()) {
201
- controller.enqueue(state.buffer);
202
- }
203
- }
204
- });
205
- }
206
- function processSSELine(line, state, controller) {
207
- const trimmedLine = line.trim();
208
- if (trimmedLine === "") {
209
- controller.enqueue(`
210
- `);
211
- state.currentEventType = null;
212
- return;
213
- }
214
- if (trimmedLine.startsWith(":")) {
215
- controller.enqueue(line + `
216
- `);
217
- return;
218
- }
219
- if (trimmedLine.startsWith("event:")) {
220
- const eventType = trimmedLine.slice(6).trim();
221
- if (!VALID_SSE_EVENT_TYPES.has(eventType)) {
222
- state.currentEventType = null;
223
- return "skip";
224
- }
225
- state.currentEventType = eventType;
226
- controller.enqueue(line + `
227
- `);
228
- return;
229
- }
230
- if (trimmedLine.startsWith("data:")) {
231
- return processDataLine(trimmedLine, line, state, controller);
232
- }
233
- controller.enqueue(line + `
234
- `);
235
- }
236
- function processDataLine(trimmedLine, originalLine, state, controller) {
237
- const dataContent = trimmedLine.slice(5).trim();
238
- if (dataContent === "[DONE]") {
239
- controller.enqueue(originalLine + `
240
- `);
241
- return;
242
- }
243
- if (state.currentEventType === null) {
244
- return "skip";
245
- }
246
- try {
247
- const data = JSON.parse(dataContent);
248
- debugLog("Event:", state.currentEventType, "Data:", JSON.stringify(data).slice(0, 500));
249
- const shouldSkip = handleEventData(data, state, controller);
250
- if (shouldSkip)
251
- return "skip";
252
- const normalized = normalizeEventData(data, state.currentEventType);
253
- controller.enqueue(`data: ${JSON.stringify(normalized)}
254
- `);
255
- } catch {
256
- controller.enqueue(originalLine + `
257
- `);
258
- }
259
- }
260
- function handleEventData(data, state, controller) {
261
- const eventType = state.currentEventType;
262
- if (eventType === "content_block_start" && typeof data.index === "number") {
263
- const blockData = data;
264
- state.trackBlockStart(blockData.index, blockData.content_block);
265
- return false;
266
- }
267
- if (eventType === "content_block_delta" && typeof data.index === "number") {
268
- return handleContentBlockDelta(data, state, controller);
269
- }
270
- if (eventType === "content_block_stop" && typeof data.index === "number") {
271
- state.startedBlocks.delete(data.index);
272
- return false;
273
- }
274
- if (eventType === "message_delta") {
275
- state.flushPendingToolBlocks(controller);
276
- if (state.startedBlocks.size > 0) {
277
- state.closeAllBlocks(controller);
278
- }
279
- emitEventHeader(controller, eventType);
280
- return false;
281
- }
282
- return false;
283
- }
284
- function handleContentBlockDelta(data, state, controller) {
285
- const index = data.index;
286
- const deltaType = data.delta?.type;
287
- if (!state.startedBlocks.has(index)) {
288
- if (deltaType === "input_json_delta") {
289
- const jsonDelta = data.delta;
290
- state.bufferToolDelta(index, state.currentEventType, data, jsonDelta.partial_json || "");
291
- return true;
292
- } else {
293
- injectTextBlockStart(index, state, controller);
294
- }
295
- } else if (deltaType === "input_json_delta" && state.hasPendingToolBlock(index)) {
296
- const jsonDelta = data.delta;
297
- state.bufferToolDelta(index, state.currentEventType, data, jsonDelta.partial_json || "");
298
- return true;
299
- } else if (deltaType === "input_json_delta") {
300
- const blockInfo = state.startedBlocks.get(index);
301
- if (blockInfo?.type === "tool_use") {
302
- const jsonDelta = data.delta;
303
- blockInfo.inputBuffer = (blockInfo.inputBuffer || "") + (jsonDelta.partial_json || "");
304
- }
305
- }
306
- fixDeltaTypeMismatch(data, state.startedBlocks.get(index));
307
- return false;
308
- }
309
- function injectTextBlockStart(index, state, controller) {
310
- state.startedBlocks.set(index, { type: "text", emitted: true });
311
- emitSSEEvent(controller, "content_block_start", {
312
- type: "content_block_start",
313
- index,
314
- content_block: { type: "text", text: "" }
315
- });
316
- emitEventHeader(controller, state.currentEventType);
317
- }
318
- function fixDeltaTypeMismatch(data, blockInfo) {
319
- if (!blockInfo || !data.delta)
320
- return;
321
- const blockType = blockInfo.type || "text";
322
- if (blockType === "tool_use" && data.delta.type === "text_delta") {
323
- const textDelta = data.delta;
324
- data.delta = {
325
- type: "input_json_delta",
326
- partial_json: textDelta.text || ""
327
- };
328
- } else if (blockType === "text" && data.delta.type === "input_json_delta") {
329
- const jsonDelta = data.delta;
330
- data.delta = {
331
- type: "text_delta",
332
- text: jsonDelta.partial_json || ""
333
- };
334
- }
335
- }
336
- function normalizeEventData(data, eventType) {
337
- const normalized = { ...data };
338
- if (!normalized.type) {
339
- normalized.type = eventType;
340
- }
341
- if (eventType === "message_start") {
342
- return normalizeMessageStart(data);
343
- }
344
- if (eventType === "content_block_start") {
345
- return normalizeContentBlockStart(normalized);
346
- }
347
- return normalized;
348
- }
349
- function normalizeMessageStart(data) {
350
- if (data.type === "message" || !data.message) {
351
- const messageData = { ...data };
352
- delete messageData.type;
353
- if (typeof messageData.model === "string" && messageData.model.includes("us.anthropic.")) {
354
- messageData.model = mapBedrockModelId(messageData.model);
355
- }
356
- return { type: "message_start", message: messageData };
357
- }
358
- if (data.message?.model?.includes("us.anthropic.")) {
359
- const normalized = {
360
- ...data,
361
- message: {
362
- ...data.message,
363
- model: mapBedrockModelId(data.message.model)
364
- }
365
- };
366
- return normalized;
367
- }
368
- return data;
369
- }
370
- function normalizeContentBlockStart(normalized) {
371
- if (!normalized.content_block) {
372
- return {
373
- ...normalized,
374
- content_block: { type: "text", text: "" }
375
- };
376
- } else if (normalized.content_block.type === "tool_use") {
377
- const toolBlock = normalized.content_block;
378
- return {
379
- ...normalized,
380
- content_block: {
381
- ...toolBlock,
382
- id: toolBlock.id || generateToolUseId(),
383
- name: toolBlock.name || "unknown"
384
- }
385
- };
386
- }
387
- return normalized;
388
- }
389
- function extractToolsFromBody(body) {
390
- if (!body)
391
- return [];
392
- try {
393
- const bodyStr = typeof body === "string" ? body : new TextDecoder().decode(body);
394
- const bodyJson = JSON.parse(bodyStr);
395
- return Array.isArray(bodyJson.tools) ? bodyJson.tools : [];
396
- } catch {
397
- return [];
398
- }
399
- }
400
- function buildRequestHeaders(input, init, apiKey) {
401
- const headers = new Headers;
402
- if (input instanceof Request) {
403
- input.headers.forEach((value, key) => headers.set(key, value));
404
- }
405
- if (init?.headers) {
406
- const initHeaders = init.headers;
407
- if (initHeaders instanceof Headers) {
408
- initHeaders.forEach((value, key) => headers.set(key, value));
409
- } else if (Array.isArray(initHeaders)) {
410
- for (const [key, value] of initHeaders) {
411
- if (value !== undefined)
412
- headers.set(key, String(value));
413
- }
414
- } else {
415
- for (const [key, value] of Object.entries(initHeaders)) {
416
- if (value !== undefined)
417
- headers.set(key, String(value));
418
- }
419
- }
420
- }
421
- headers.set("x-api-key", apiKey);
422
- if (!headers.has("anthropic-version")) {
423
- headers.set("anthropic-version", "2023-06-01");
424
- }
425
- return headers;
426
- }
427
- function normalizeApiUrl(input) {
428
- const url = input instanceof Request ? input.url : String(input);
429
- const urlObj = new URL(url);
430
- if (!urlObj.pathname.includes("/v1/")) {
431
- if (urlObj.pathname.includes("/api/")) {
432
- urlObj.pathname = urlObj.pathname.replace("/api/", "/api/v1/");
433
- } else {
434
- urlObj.pathname = urlObj.pathname.replace(/^\/?/, "/v1/");
435
- }
436
- }
437
- return urlObj.toString();
438
- }
439
- function transformStreamingResponse(response, tools) {
440
- const reader = response.body.getReader();
441
- const decoder = new TextDecoder;
442
- const encoder = new TextEncoder;
443
- const transformStream = createSSETransformStream(tools);
444
- const writer = transformStream.writable.getWriter();
445
- (async () => {
446
- try {
447
- while (true) {
448
- const { done, value } = await reader.read();
449
- if (done) {
450
- await writer.close();
451
- break;
452
- }
453
- await writer.write(decoder.decode(value, { stream: true }));
454
- }
455
- } catch (error) {
456
- await writer.abort(error);
457
- }
458
- })();
459
- const transformedBody = transformStream.readable.pipeThrough(new TransformStream({
460
- transform(chunk, controller) {
461
- controller.enqueue(encoder.encode(chunk));
462
- }
463
- }));
464
- return new Response(transformedBody, {
465
- status: response.status,
466
- statusText: response.statusText,
467
- headers: response.headers
468
- });
469
- }
470
- async function transformJsonResponse(response) {
471
- try {
472
- const text = await response.text();
473
- const data = JSON.parse(text);
474
- if (data.model?.includes("us.anthropic.")) {
475
- data.model = mapBedrockModelId(data.model);
476
- }
477
- return new Response(JSON.stringify(data), {
478
- status: response.status,
479
- statusText: response.statusText,
480
- headers: response.headers
481
- });
482
- } catch {
483
- return response;
484
- }
485
- }
486
- function createCRSFetch(getAuth) {
487
- return async function crsFetch(input, init) {
488
- const auth = await getAuth();
489
- if (!auth?.key) {
490
- return fetch(input, init);
491
- }
492
- const url = normalizeApiUrl(input);
493
- const headers = buildRequestHeaders(input, init, auth.key);
494
- const tools = extractToolsFromBody(init?.body);
495
- const response = await fetch(url, { ...init, headers });
496
- const contentType = response.headers.get("content-type") || "";
497
- if (contentType.includes("text/event-stream") && response.body) {
498
- return transformStreamingResponse(response, tools);
499
- }
500
- if (contentType.includes("application/json")) {
501
- return transformJsonResponse(response);
502
- }
503
- return response;
504
- };
505
- }
506
- var CRSAuthPlugin = async () => {
507
- return {
508
- auth: {
509
- provider: PROVIDER_ID,
510
- async loader(getAuth, provider) {
511
- let baseURL = provider?.options?.baseURL;
512
- if (baseURL && !baseURL.includes("/v1")) {
513
- baseURL = baseURL.replace(/\/?$/, "/v1/");
514
- }
515
- let apiKey = provider?.options?.apiKey;
516
- if (!apiKey) {
517
- const auth = await getAuth();
518
- apiKey = auth?.type === "api" ? auth.key : undefined;
519
- }
520
- if (!apiKey) {
521
- return {};
522
- }
523
- return {
524
- apiKey: "",
525
- baseURL,
526
- fetch: createCRSFetch(() => Promise.resolve({ key: apiKey }))
527
- };
528
- },
529
- methods: [
530
- {
531
- label: "Enter CRS API Key",
532
- type: "api",
533
- prompts: [
534
- {
535
- type: "text",
536
- message: "Enter your CRS API Key (cr_...)",
537
- key: "apiKey"
538
- }
539
- ]
540
- }
541
- ]
542
- }
543
- };
544
- };
545
- export {
546
- CRSAuthPlugin
547
- };