@opencode/ai 0.0.0-dev-19544 → 0.0.0-dev-19550
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/dist/protocols/bedrock-converse.js +15 -11
- package/dist/protocols/mistral-chat.js +5 -33
- package/dist/protocols/utils/bedrock-media.d.ts +10 -8
- package/dist/protocols/utils/bedrock-media.js +29 -5
- package/dist/protocols/utils/mistral-tool-id.d.ts +5 -0
- package/dist/protocols/utils/mistral-tool-id.js +31 -0
- package/package.json +3 -3
|
@@ -10,6 +10,7 @@ import { BedrockAuth } from "./utils/bedrock-auth.js";
|
|
|
10
10
|
import { BedrockCache } from "./utils/bedrock-cache.js";
|
|
11
11
|
import { BedrockMedia } from "./utils/bedrock-media.js";
|
|
12
12
|
import { Lifecycle } from "./utils/lifecycle.js";
|
|
13
|
+
import { MistralToolID } from "./utils/mistral-tool-id.js";
|
|
13
14
|
import { ToolSchemaProjection } from "./utils/tool-schema.js";
|
|
14
15
|
import { ToolStream } from "./utils/tool-stream.js";
|
|
15
16
|
const ADAPTER = "bedrock-converse";
|
|
@@ -201,15 +202,15 @@ const removeEmptyToolInputKeys = (input) => {
|
|
|
201
202
|
return input;
|
|
202
203
|
return Object.fromEntries(Object.entries(input).flatMap(([key, value]) => (key === "" ? [] : [[key, removeEmptyToolInputKeys(value)]])));
|
|
203
204
|
};
|
|
204
|
-
const lowerToolCall = (part) => ({
|
|
205
|
+
const lowerToolCall = (part, normalizeID) => ({
|
|
205
206
|
toolUse: {
|
|
206
|
-
toolUseId: part.id,
|
|
207
|
+
toolUseId: normalizeID(part.id),
|
|
207
208
|
// Models can emit names that Converse rejects when replayed in history.
|
|
208
209
|
name: part.name.replace(/[^a-zA-Z0-9_-]/g, "_").slice(0, 64) || "_",
|
|
209
210
|
input: removeEmptyToolInputKeys(part.input),
|
|
210
211
|
},
|
|
211
212
|
});
|
|
212
|
-
const lowerToolResultContent = Effect.fn("BedrockConverse.lowerToolResultContent")(function* (part) {
|
|
213
|
+
const lowerToolResultContent = Effect.fn("BedrockConverse.lowerToolResultContent")(function* (part, documentNames) {
|
|
213
214
|
if (part.result.type === "text" || part.result.type === "error")
|
|
214
215
|
return [{ text: ProviderShared.toolResultText(part) }];
|
|
215
216
|
if (part.result.type === "json")
|
|
@@ -225,22 +226,25 @@ const lowerToolResultContent = Effect.fn("BedrockConverse.lowerToolResultContent
|
|
|
225
226
|
mediaType: item.mime,
|
|
226
227
|
data: item.uri,
|
|
227
228
|
filename: item.name,
|
|
228
|
-
});
|
|
229
|
-
content.push(media);
|
|
229
|
+
}, documentNames);
|
|
230
|
+
content.push(...media);
|
|
230
231
|
}
|
|
231
232
|
return content;
|
|
232
233
|
});
|
|
233
|
-
const lowerToolResult = Effect.fn("BedrockConverse.lowerToolResult")(function* (part) {
|
|
234
|
+
const lowerToolResult = Effect.fn("BedrockConverse.lowerToolResult")(function* (part, documentNames, normalizeID) {
|
|
234
235
|
return {
|
|
235
236
|
toolResult: {
|
|
236
|
-
toolUseId: part.id,
|
|
237
|
-
content: yield* lowerToolResultContent(part),
|
|
237
|
+
toolUseId: normalizeID(part.id),
|
|
238
|
+
content: yield* lowerToolResultContent(part, documentNames),
|
|
238
239
|
status: part.result.type === "error" ? "error" : "success",
|
|
239
240
|
},
|
|
240
241
|
};
|
|
241
242
|
});
|
|
242
243
|
const lowerMessages = Effect.fn("BedrockConverse.lowerMessages")(function* (request, breakpoints) {
|
|
243
244
|
const messages = [];
|
|
245
|
+
const documentNames = new Set();
|
|
246
|
+
// Mistral can reject replay IDs even when they satisfy Converse's broader ID syntax.
|
|
247
|
+
const normalizeID = request.model.id.includes("mistral.") ? MistralToolID.normalizer(request) : (id) => id;
|
|
244
248
|
const providerMetadataKey = request.model.route.providerMetadataKey ?? String(request.model.provider);
|
|
245
249
|
for (const message of request.messages) {
|
|
246
250
|
if (message.role === "system") {
|
|
@@ -263,7 +267,7 @@ const lowerMessages = Effect.fn("BedrockConverse.lowerMessages")(function* (requ
|
|
|
263
267
|
continue;
|
|
264
268
|
}
|
|
265
269
|
if (part.type === "media") {
|
|
266
|
-
content.push(yield* BedrockMedia.lower(part));
|
|
270
|
+
content.push(...(yield* BedrockMedia.lower(part, documentNames)));
|
|
267
271
|
continue;
|
|
268
272
|
}
|
|
269
273
|
}
|
|
@@ -305,7 +309,7 @@ const lowerMessages = Effect.fn("BedrockConverse.lowerMessages")(function* (requ
|
|
|
305
309
|
continue;
|
|
306
310
|
}
|
|
307
311
|
if (part.type === "tool-call") {
|
|
308
|
-
content.push(lowerToolCall(part));
|
|
312
|
+
content.push(lowerToolCall(part, normalizeID));
|
|
309
313
|
continue;
|
|
310
314
|
}
|
|
311
315
|
}
|
|
@@ -317,7 +321,7 @@ const lowerMessages = Effect.fn("BedrockConverse.lowerMessages")(function* (requ
|
|
|
317
321
|
for (const part of message.content) {
|
|
318
322
|
if (!ProviderShared.supportsContent(part, ["tool-result"]))
|
|
319
323
|
return yield* ProviderShared.unsupportedContent("Bedrock Converse", "tool", ["tool-result"]);
|
|
320
|
-
content.push(yield* lowerToolResult(part));
|
|
324
|
+
content.push(yield* lowerToolResult(part, documentNames, normalizeID));
|
|
321
325
|
const cachePoint = BedrockCache.block(breakpoints, part.cache);
|
|
322
326
|
if (cachePoint)
|
|
323
327
|
content.push(cachePoint);
|
|
@@ -9,10 +9,10 @@ import { AIError, InvalidProviderOutputError, LLMEvent, ProviderInternalError, U
|
|
|
9
9
|
import { classifyProviderFailure } from "../provider-error.js";
|
|
10
10
|
import { JsonObject, optionalArray, optionalNull, ProviderShared } from "./shared.js";
|
|
11
11
|
import { Lifecycle } from "./utils/lifecycle.js";
|
|
12
|
+
import { MistralToolID } from "./utils/mistral-tool-id.js";
|
|
12
13
|
import { ToolStream } from "./utils/tool-stream.js";
|
|
13
14
|
const ADAPTER = "mistral-chat";
|
|
14
15
|
const DONE = "[DONE]";
|
|
15
|
-
const TOOL_ID = /^[A-Za-z0-9]{9}$/;
|
|
16
16
|
export const DEFAULT_BASE_URL = "https://api.mistral.ai/v1";
|
|
17
17
|
export const PATH = "/chat/completions";
|
|
18
18
|
const MistralTextContent = Schema.Struct({
|
|
@@ -140,34 +140,6 @@ const MistralEvent = Schema.StructWithRest(Schema.Struct({
|
|
|
140
140
|
error: optionalNull(MistralError),
|
|
141
141
|
}), [Schema.Record(Schema.String, Schema.Unknown)]);
|
|
142
142
|
const MistralStreamEvent = Schema.Union([Schema.Literal(DONE), Protocol.jsonEvent(MistralEvent)]);
|
|
143
|
-
const hashID = (value) => {
|
|
144
|
-
const hash = (seed) => {
|
|
145
|
-
let result = seed;
|
|
146
|
-
for (const char of value)
|
|
147
|
-
result = Math.imul(result ^ char.charCodeAt(0), 16777619);
|
|
148
|
-
return (result >>> 0).toString(36);
|
|
149
|
-
};
|
|
150
|
-
return `${hash(2166136261).padStart(7, "0")}${hash(2246822519).padStart(7, "0")}`.slice(-9);
|
|
151
|
-
};
|
|
152
|
-
const toolIDNormalizer = (request) => {
|
|
153
|
-
const ids = request.messages.flatMap((message) => message.content.flatMap((part) => (part.type === "tool-call" || part.type === "tool-result" ? [part.id] : [])));
|
|
154
|
-
const used = new Set(ids.filter((id) => TOOL_ID.test(id)));
|
|
155
|
-
const normalized = new Map();
|
|
156
|
-
return (id) => {
|
|
157
|
-
if (TOOL_ID.test(id))
|
|
158
|
-
return id;
|
|
159
|
-
const previous = normalized.get(id);
|
|
160
|
-
if (previous)
|
|
161
|
-
return previous;
|
|
162
|
-
let attempt = 0;
|
|
163
|
-
let candidate = hashID(id);
|
|
164
|
-
while (used.has(candidate))
|
|
165
|
-
candidate = hashID(`${id}:${++attempt}`);
|
|
166
|
-
used.add(candidate);
|
|
167
|
-
normalized.set(id, candidate);
|
|
168
|
-
return candidate;
|
|
169
|
-
};
|
|
170
|
-
};
|
|
171
143
|
const lowerMedia = Effect.fn("MistralChat.lowerMedia")(function* (part) {
|
|
172
144
|
const media = ProviderShared.normalizeMedia(part);
|
|
173
145
|
const url = typeof part.data === "string" && /^(?:https?:|data:)/.test(part.data) ? part.data : media.dataUrl;
|
|
@@ -269,7 +241,7 @@ const lowerToolResults = Effect.fn("MistralChat.lowerToolResults")(function* (me
|
|
|
269
241
|
return output;
|
|
270
242
|
});
|
|
271
243
|
const lowerMessages = Effect.fn("MistralChat.lowerMessages")(function* (request) {
|
|
272
|
-
const normalizeID =
|
|
244
|
+
const normalizeID = MistralToolID.normalizer(request);
|
|
273
245
|
const messages = request.system.length === 0 ? [] : [{ role: "system", content: ProviderShared.joinText(request.system) }];
|
|
274
246
|
for (const message of request.messages) {
|
|
275
247
|
if (message.role === "system") {
|
|
@@ -439,15 +411,15 @@ const appendContent = (state, events, content) => {
|
|
|
439
411
|
}, state);
|
|
440
412
|
};
|
|
441
413
|
const normalizeStreamToolID = (state, source) => {
|
|
442
|
-
if (
|
|
414
|
+
if (MistralToolID.valid.test(source))
|
|
443
415
|
return { id: source, state: { ...state, usedToolIDs: new Set([...state.usedToolIDs, source]) } };
|
|
444
416
|
const previous = state.toolIDs.get(source);
|
|
445
417
|
if (previous)
|
|
446
418
|
return { id: previous, state };
|
|
447
419
|
let attempt = 0;
|
|
448
|
-
let id =
|
|
420
|
+
let id = MistralToolID.hash(source);
|
|
449
421
|
while (state.usedToolIDs.has(id))
|
|
450
|
-
id =
|
|
422
|
+
id = MistralToolID.hash(`${source}:${++attempt}`);
|
|
451
423
|
return {
|
|
452
424
|
id,
|
|
453
425
|
state: {
|
|
@@ -36,7 +36,14 @@ export declare const lower: (part: {
|
|
|
36
36
|
};
|
|
37
37
|
} | undefined;
|
|
38
38
|
readonly filename?: string | undefined;
|
|
39
|
-
}) => Effect.Effect<{
|
|
39
|
+
}, documentNames: Set<string>) => Effect.Effect<{
|
|
40
|
+
image: {
|
|
41
|
+
format: "png" | "jpeg" | "gif" | "webp";
|
|
42
|
+
source: {
|
|
43
|
+
bytes: string;
|
|
44
|
+
};
|
|
45
|
+
};
|
|
46
|
+
}[] | ({
|
|
40
47
|
readonly document: {
|
|
41
48
|
readonly format: "pdf" | "csv" | "doc" | "docx" | "xls" | "xlsx" | "html" | "txt" | "md";
|
|
42
49
|
readonly name: string;
|
|
@@ -45,11 +52,6 @@ export declare const lower: (part: {
|
|
|
45
52
|
};
|
|
46
53
|
};
|
|
47
54
|
} | {
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
source: {
|
|
51
|
-
bytes: string;
|
|
52
|
-
};
|
|
53
|
-
};
|
|
54
|
-
}, import("../../schema/errors.js").AIError, never>;
|
|
55
|
+
text: string;
|
|
56
|
+
})[], import("../../schema/errors.js").AIError, never>;
|
|
55
57
|
export * as BedrockMedia from "./bedrock-media.js";
|
|
@@ -44,6 +44,23 @@ const documentBlock = (name, format, bytes) => ({
|
|
|
44
44
|
source: { bytes },
|
|
45
45
|
},
|
|
46
46
|
});
|
|
47
|
+
function documentName(filename, names) {
|
|
48
|
+
const base = (filename ?? "")
|
|
49
|
+
.replace(/\.[^.]*$/, "")
|
|
50
|
+
.replace(/[^a-zA-Z0-9 ()[\]-]/g, " ")
|
|
51
|
+
.replace(/\s+/g, " ")
|
|
52
|
+
.trim()
|
|
53
|
+
.slice(0, 200)
|
|
54
|
+
.trim() || "document";
|
|
55
|
+
let name = base;
|
|
56
|
+
// Converse requires labels to be unique across the entire request, including tool results.
|
|
57
|
+
for (let index = 2; names.has(name); index++) {
|
|
58
|
+
const suffix = ` ${index}`;
|
|
59
|
+
name = `${base.slice(0, 200 - suffix.length).trimEnd()}${suffix}`;
|
|
60
|
+
}
|
|
61
|
+
names.add(name);
|
|
62
|
+
return name;
|
|
63
|
+
}
|
|
47
64
|
const mediaBase64 = Effect.fn("BedrockMedia.mediaBase64")(function* (part) {
|
|
48
65
|
const media = ProviderShared.normalizeMedia(part);
|
|
49
66
|
const bytes = yield* Effect.fromResult(Encoding.decodeBase64(media.base64)).pipe(Effect.mapError((cause) => ProviderShared.invalidRequest("Bedrock Converse media data must be valid base64", cause)));
|
|
@@ -54,19 +71,26 @@ const mediaBase64 = Effect.fn("BedrockMedia.mediaBase64")(function* (part) {
|
|
|
54
71
|
// document block. Image MIME types not in `IMAGE_FORMATS` (e.g. `image/svg+xml`)
|
|
55
72
|
// get an image-specific error so the caller knows it's a format-support issue,
|
|
56
73
|
// not a kind-detection issue.
|
|
57
|
-
export const lower = Effect.fn("BedrockMedia.lower")(function* (part) {
|
|
74
|
+
export const lower = Effect.fn("BedrockMedia.lower")(function* (part, documentNames) {
|
|
58
75
|
const mime = part.mediaType.toLowerCase();
|
|
59
76
|
const imageFormat = IMAGE_FORMATS[mime];
|
|
60
77
|
if (imageFormat) {
|
|
61
|
-
return { image: { format: imageFormat, source: { bytes: yield* mediaBase64(part) } } };
|
|
78
|
+
return [{ image: { format: imageFormat, source: { bytes: yield* mediaBase64(part) } } }];
|
|
62
79
|
}
|
|
63
80
|
if (mime.startsWith("image/"))
|
|
64
81
|
return yield* ProviderShared.invalidRequest(`Bedrock Converse does not support image media type ${part.mediaType}`);
|
|
65
82
|
const documentFormat = DOCUMENT_FORMATS[mime];
|
|
66
83
|
if (documentFormat) {
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
return
|
|
84
|
+
const name = documentName(part.filename, documentNames);
|
|
85
|
+
const block = documentBlock(name, documentFormat, yield* mediaBase64(part));
|
|
86
|
+
return part.filename !== undefined && part.filename !== name
|
|
87
|
+
? [
|
|
88
|
+
{
|
|
89
|
+
text: `Attached file ${ProviderShared.encodeJson(part.filename)} has document label ${ProviderShared.encodeJson(name)}.`,
|
|
90
|
+
},
|
|
91
|
+
block,
|
|
92
|
+
]
|
|
93
|
+
: [block];
|
|
70
94
|
}
|
|
71
95
|
return yield* ProviderShared.invalidRequest(`Bedrock Converse does not support media type ${part.mediaType}`);
|
|
72
96
|
});
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import type { LLMRequest } from "../../schema/index.js";
|
|
2
|
+
export declare const valid: RegExp;
|
|
3
|
+
export declare const hash: (value: string) => string;
|
|
4
|
+
export declare const normalizer: (request: LLMRequest) => (id: string) => string;
|
|
5
|
+
export * as MistralToolID from "./mistral-tool-id.js";
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
export const valid = /^[A-Za-z0-9]{9}$/;
|
|
2
|
+
export const hash = (value) => {
|
|
3
|
+
const hash = (seed) => {
|
|
4
|
+
let result = seed;
|
|
5
|
+
for (const char of value)
|
|
6
|
+
result = Math.imul(result ^ char.charCodeAt(0), 16777619);
|
|
7
|
+
return (result >>> 0).toString(36);
|
|
8
|
+
};
|
|
9
|
+
return `${hash(2166136261).padStart(7, "0")}${hash(2246822519).padStart(7, "0")}`.slice(-9);
|
|
10
|
+
};
|
|
11
|
+
export const normalizer = (request) => {
|
|
12
|
+
const ids = request.messages.flatMap((message) => message.content.flatMap((part) => (part.type === "tool-call" || part.type === "tool-result" ? [part.id] : [])));
|
|
13
|
+
// Reserve valid IDs before projecting any history, including IDs encountered later.
|
|
14
|
+
const used = new Set(ids.filter((id) => valid.test(id)));
|
|
15
|
+
const normalized = new Map();
|
|
16
|
+
return (id) => {
|
|
17
|
+
if (valid.test(id))
|
|
18
|
+
return id;
|
|
19
|
+
const previous = normalized.get(id);
|
|
20
|
+
if (previous)
|
|
21
|
+
return previous;
|
|
22
|
+
let attempt = 0;
|
|
23
|
+
let candidate = hash(id);
|
|
24
|
+
while (used.has(candidate))
|
|
25
|
+
candidate = hash(`${id}:${++attempt}`);
|
|
26
|
+
used.add(candidate);
|
|
27
|
+
normalized.set(id, candidate);
|
|
28
|
+
return candidate;
|
|
29
|
+
};
|
|
30
|
+
};
|
|
31
|
+
export * as MistralToolID from "./mistral-tool-id.js";
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"$schema": "https://json.schemastore.org/package.json",
|
|
3
|
-
"version": "0.0.0-dev-
|
|
3
|
+
"version": "0.0.0-dev-19550",
|
|
4
4
|
"name": "@opencode/ai",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -30,7 +30,7 @@
|
|
|
30
30
|
"devDependencies": {
|
|
31
31
|
"@clack/prompts": "1.0.0-alpha.1",
|
|
32
32
|
"@effect/platform-node": "4.0.0-rc.112",
|
|
33
|
-
"@opencode/http-recorder": "0.0.0-dev-
|
|
33
|
+
"@opencode/http-recorder": "0.0.0-dev-19550",
|
|
34
34
|
"@tsconfig/bun": "1.0.9",
|
|
35
35
|
"@types/bun": "1.4.0",
|
|
36
36
|
"@typescript/native-preview": "7.0.0-dev.20251207.1",
|
|
@@ -40,7 +40,7 @@
|
|
|
40
40
|
"@aws-sdk/credential-providers": "3.1057.0",
|
|
41
41
|
"@smithy/eventstream-codec": "4.2.14",
|
|
42
42
|
"@smithy/util-utf8": "4.2.2",
|
|
43
|
-
"@opencode/schema": "0.0.0-dev-
|
|
43
|
+
"@opencode/schema": "0.0.0-dev-19550",
|
|
44
44
|
"aws4fetch": "1.0.20",
|
|
45
45
|
"effect": "4.0.0-rc.112",
|
|
46
46
|
"google-auth-library": "10.5.0"
|