@xsai/text-messages 0.0.0-stage → 1.0.0-beta.1
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/LICENSE.md +21 -0
- package/README.md +27 -2
- package/dist/index.d.ts +59 -0
- package/dist/index.js +412 -0
- package/package.json +46 -3
package/LICENSE.md
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2024 Moeru AI
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,3 +1,28 @@
|
|
|
1
|
-
#
|
|
1
|
+
# @xsai/text-messages
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
<!-- automd:file src="/docs/snippets/text-messages.md" -->
|
|
4
|
+
|
|
5
|
+
```sh
|
|
6
|
+
pnpm add @xsai/text @xsai/text-messages
|
|
7
|
+
```
|
|
8
|
+
|
|
9
|
+
```ts
|
|
10
|
+
import { generateText } from '@xsai/text'
|
|
11
|
+
import { messages } from '@xsai/text-messages'
|
|
12
|
+
|
|
13
|
+
const model = messages({
|
|
14
|
+
apiKey: process.env.ANTHROPIC_API_KEY,
|
|
15
|
+
baseURL: 'https://api.anthropic.com/v1/',
|
|
16
|
+
model: 'claude-haiku-5.5',
|
|
17
|
+
})
|
|
18
|
+
|
|
19
|
+
const { text } = await generateText(model, {
|
|
20
|
+
input: 'Say hello.',
|
|
21
|
+
maxOutputTokens: 128,
|
|
22
|
+
})
|
|
23
|
+
console.log(text)
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
<!-- /automd -->
|
|
27
|
+
|
|
28
|
+
Read the [documentation](https://xsai.js.org/text/adapters).
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { HttpOptions } from '@xsai/shared';
|
|
2
|
+
import { LanguageModel } from '@xsai/text';
|
|
3
|
+
|
|
4
|
+
interface MessagesCitation {
|
|
5
|
+
[key: string]: unknown;
|
|
6
|
+
type: string;
|
|
7
|
+
}
|
|
8
|
+
interface MessagesPartMetadata {
|
|
9
|
+
citations?: MessagesCitation[];
|
|
10
|
+
signature?: string;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
interface MessagesCacheControl {
|
|
14
|
+
ttl?: '1h' | '5m';
|
|
15
|
+
type: 'ephemeral';
|
|
16
|
+
}
|
|
17
|
+
interface MessagesMcpServer {
|
|
18
|
+
authorization_token?: string;
|
|
19
|
+
name: string;
|
|
20
|
+
type: 'url';
|
|
21
|
+
url: string;
|
|
22
|
+
}
|
|
23
|
+
interface MessagesProviderOptions {
|
|
24
|
+
betas?: readonly string[];
|
|
25
|
+
/** Automatic caching at the last cacheable part in the request. */
|
|
26
|
+
cacheControl?: MessagesCacheControl;
|
|
27
|
+
mcpServers?: readonly MessagesMcpServer[];
|
|
28
|
+
stopSequences?: readonly string[];
|
|
29
|
+
/** Thinking mode and display. Effort stays in `reasoningEffort`. */
|
|
30
|
+
thinking?: MessagesThinkingConfig;
|
|
31
|
+
tools?: readonly MessagesProviderTool[];
|
|
32
|
+
}
|
|
33
|
+
interface MessagesProviderTool {
|
|
34
|
+
[key: string]: unknown;
|
|
35
|
+
type: string;
|
|
36
|
+
}
|
|
37
|
+
type MessagesThinkingConfig = {
|
|
38
|
+
budget_tokens: number;
|
|
39
|
+
display?: 'omitted' | 'summarized';
|
|
40
|
+
type: 'enabled';
|
|
41
|
+
} | {
|
|
42
|
+
display?: 'omitted' | 'summarized';
|
|
43
|
+
type: 'adaptive';
|
|
44
|
+
} | {
|
|
45
|
+
type: 'between_tools' | 'disabled';
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
declare module '@xsai/text' {
|
|
49
|
+
interface LanguageModelProviderOptions {
|
|
50
|
+
messages?: MessagesProviderOptions;
|
|
51
|
+
}
|
|
52
|
+
interface PartProviderMetadata {
|
|
53
|
+
messages?: MessagesPartMetadata;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
declare const messages: (options: HttpOptions) => LanguageModel;
|
|
57
|
+
|
|
58
|
+
export { messages };
|
|
59
|
+
export type { MessagesCacheControl, MessagesCitation, MessagesMcpServer, MessagesPartMetadata, MessagesProviderOptions, MessagesProviderTool, MessagesThinkingConfig };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,412 @@
|
|
|
1
|
+
import { XSAIError, requestURL } from '@xsai/shared';
|
|
2
|
+
import { WireEventStream, resolveSchema, wireRequest } from '@xsai/text/internal';
|
|
3
|
+
|
|
4
|
+
const mapStop = (FinishReason) => {
|
|
5
|
+
switch (FinishReason) {
|
|
6
|
+
case "content_filter":
|
|
7
|
+
return { reason: "content-filter", status: "incomplete" };
|
|
8
|
+
case "end_turn":
|
|
9
|
+
case "stop_sequence":
|
|
10
|
+
return { reason: "stop", status: "completed" };
|
|
11
|
+
case "max_output_tokens":
|
|
12
|
+
case "max_tokens":
|
|
13
|
+
return { reason: "length", status: "incomplete" };
|
|
14
|
+
case null:
|
|
15
|
+
case void 0:
|
|
16
|
+
return { status: "completed" };
|
|
17
|
+
case "refusal":
|
|
18
|
+
return { reason: "refusal", status: "completed" };
|
|
19
|
+
case "tool_use":
|
|
20
|
+
return { reason: "tool-calls", status: "completed" };
|
|
21
|
+
default:
|
|
22
|
+
return { reason: FinishReason, status: "completed" };
|
|
23
|
+
}
|
|
24
|
+
};
|
|
25
|
+
const mergeUsage = (start, delta) => {
|
|
26
|
+
const inputTokens = delta?.input_tokens ?? start?.input_tokens;
|
|
27
|
+
const outputTokens = delta?.output_tokens ?? start?.output_tokens;
|
|
28
|
+
if (inputTokens == null && outputTokens == null)
|
|
29
|
+
return void 0;
|
|
30
|
+
const input = inputTokens ?? 0;
|
|
31
|
+
const output = outputTokens ?? 0;
|
|
32
|
+
const cacheRead = start?.cache_read_input_tokens ?? delta?.cache_read_input_tokens;
|
|
33
|
+
const cacheCreation = start?.cache_creation_input_tokens ?? delta?.cache_creation_input_tokens;
|
|
34
|
+
return {
|
|
35
|
+
cacheCreationInputTokens: cacheCreation,
|
|
36
|
+
cacheReadInputTokens: cacheRead,
|
|
37
|
+
inputTokens: input,
|
|
38
|
+
outputTokens: output,
|
|
39
|
+
reasoningTokens: delta?.output_tokens_details?.thinking_tokens,
|
|
40
|
+
// Anthropic reports no total and counts cache buckets separately from input_tokens.
|
|
41
|
+
totalTokens: input + output + (cacheRead ?? 0) + (cacheCreation ?? 0)
|
|
42
|
+
};
|
|
43
|
+
};
|
|
44
|
+
class MessagesEventStream extends WireEventStream {
|
|
45
|
+
citations = /* @__PURE__ */ new Map();
|
|
46
|
+
deltaUsage;
|
|
47
|
+
FinishReason;
|
|
48
|
+
providerBlocks = /* @__PURE__ */ new Map();
|
|
49
|
+
signatures = /* @__PURE__ */ new Map();
|
|
50
|
+
source;
|
|
51
|
+
startUsage;
|
|
52
|
+
constructor(source, includeRawEvents = false) {
|
|
53
|
+
super((event, builder) => {
|
|
54
|
+
switch (event.type) {
|
|
55
|
+
case "content_block_delta":
|
|
56
|
+
this.onDelta(builder, event);
|
|
57
|
+
break;
|
|
58
|
+
case "content_block_start":
|
|
59
|
+
this.onStart(builder, event);
|
|
60
|
+
break;
|
|
61
|
+
case "content_block_stop":
|
|
62
|
+
this.onStop(builder, event);
|
|
63
|
+
break;
|
|
64
|
+
case "error":
|
|
65
|
+
builder.fail(new XSAIError("model-error", event.error.message, {
|
|
66
|
+
cause: event.error
|
|
67
|
+
}));
|
|
68
|
+
break;
|
|
69
|
+
case "message_delta":
|
|
70
|
+
this.FinishReason = event.delta.stop_reason;
|
|
71
|
+
this.deltaUsage = event.usage;
|
|
72
|
+
break;
|
|
73
|
+
case "message_start":
|
|
74
|
+
builder.meta({ messageId: event.message.id });
|
|
75
|
+
this.startUsage = event.message.usage;
|
|
76
|
+
break;
|
|
77
|
+
case "message_stop": {
|
|
78
|
+
const usage = mergeUsage(this.startUsage, this.deltaUsage);
|
|
79
|
+
if (usage != null)
|
|
80
|
+
builder.meta({ usage });
|
|
81
|
+
const finish = mapStop(this.FinishReason);
|
|
82
|
+
builder.done(finish.status, finish.reason);
|
|
83
|
+
break;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
}, includeRawEvents);
|
|
87
|
+
this.source = source;
|
|
88
|
+
}
|
|
89
|
+
onDelta(builder, event) {
|
|
90
|
+
switch (event.delta.type) {
|
|
91
|
+
case "citations_delta": {
|
|
92
|
+
const citations = this.citations.get(event.index);
|
|
93
|
+
if (citations == null)
|
|
94
|
+
this.citations.set(event.index, [event.delta.citation]);
|
|
95
|
+
else
|
|
96
|
+
citations.push(event.delta.citation);
|
|
97
|
+
break;
|
|
98
|
+
}
|
|
99
|
+
case "input_json_delta": {
|
|
100
|
+
const provider = this.providerBlocks.get(event.index);
|
|
101
|
+
if (provider == null)
|
|
102
|
+
builder.delta(event.index, event.delta.partial_json);
|
|
103
|
+
else
|
|
104
|
+
provider.input += event.delta.partial_json;
|
|
105
|
+
break;
|
|
106
|
+
}
|
|
107
|
+
case "signature_delta":
|
|
108
|
+
this.signatures.set(event.index, (this.signatures.get(event.index) ?? "") + event.delta.signature);
|
|
109
|
+
break;
|
|
110
|
+
case "text_delta":
|
|
111
|
+
builder.delta(event.index, event.delta.text);
|
|
112
|
+
break;
|
|
113
|
+
case "thinking_delta":
|
|
114
|
+
builder.delta(event.index, event.delta.thinking);
|
|
115
|
+
break;
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
onStart(builder, event) {
|
|
119
|
+
const block = event.content_block;
|
|
120
|
+
switch (block.type) {
|
|
121
|
+
case "document":
|
|
122
|
+
case "image":
|
|
123
|
+
case "tool_result":
|
|
124
|
+
break;
|
|
125
|
+
case "redacted_thinking":
|
|
126
|
+
builder.start(event.index, "reasoning");
|
|
127
|
+
builder.end(event.index, {
|
|
128
|
+
content: { content: [{ text: block.data, type: "redacted" }], type: "reasoning" }
|
|
129
|
+
});
|
|
130
|
+
break;
|
|
131
|
+
case "text":
|
|
132
|
+
builder.start(event.index, "text");
|
|
133
|
+
if (block.citations != null && block.citations.length > 0)
|
|
134
|
+
this.citations.set(event.index, [...block.citations]);
|
|
135
|
+
if (block.text !== "")
|
|
136
|
+
builder.delta(event.index, block.text);
|
|
137
|
+
break;
|
|
138
|
+
case "thinking":
|
|
139
|
+
builder.start(event.index, "reasoning");
|
|
140
|
+
if (block.thinking !== "")
|
|
141
|
+
builder.delta(event.index, block.thinking);
|
|
142
|
+
if (block.signature != null)
|
|
143
|
+
this.signatures.set(event.index, block.signature);
|
|
144
|
+
break;
|
|
145
|
+
case "tool_use":
|
|
146
|
+
builder.start(event.index, "tool-call", { callId: block.id, id: block.id, name: block.name });
|
|
147
|
+
if (typeof block.input === "object" && block.input !== null && Object.keys(block.input).length > 0)
|
|
148
|
+
builder.delta(event.index, JSON.stringify(block.input));
|
|
149
|
+
break;
|
|
150
|
+
default:
|
|
151
|
+
builder.start(event.index, "provider", { source: this.source });
|
|
152
|
+
this.providerBlocks.set(event.index, { block, input: "" });
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
onStop(builder, event) {
|
|
156
|
+
const provider = this.providerBlocks.get(event.index);
|
|
157
|
+
this.providerBlocks.delete(event.index);
|
|
158
|
+
if (provider != null) {
|
|
159
|
+
const value = provider.input === "" ? provider.block : { ...provider.block, input: JSON.parse(provider.input) };
|
|
160
|
+
builder.end(event.index, { content: { source: this.source, type: "provider", value } });
|
|
161
|
+
return;
|
|
162
|
+
}
|
|
163
|
+
const signature = this.signatures.get(event.index);
|
|
164
|
+
this.signatures.delete(event.index);
|
|
165
|
+
const citations = this.citations.get(event.index);
|
|
166
|
+
this.citations.delete(event.index);
|
|
167
|
+
builder.end(event.index, signature == null && citations == null ? {} : { providerMetadata: { messages: {
|
|
168
|
+
...citations == null ? {} : { citations },
|
|
169
|
+
...signature == null ? {} : { signature }
|
|
170
|
+
} } });
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
const SUPPORTED_FORMATS = /* @__PURE__ */ new Set([
|
|
175
|
+
"date",
|
|
176
|
+
"date-time",
|
|
177
|
+
"duration",
|
|
178
|
+
"email",
|
|
179
|
+
"hostname",
|
|
180
|
+
"ipv4",
|
|
181
|
+
"ipv6",
|
|
182
|
+
"time",
|
|
183
|
+
"uri",
|
|
184
|
+
"uuid"
|
|
185
|
+
]);
|
|
186
|
+
const normalizeSchema = (schema) => {
|
|
187
|
+
const normalized = { ...schema };
|
|
188
|
+
const normalize = (schema2) => typeof schema2 === "boolean" ? schema2 : normalizeSchema(schema2);
|
|
189
|
+
if (normalized.type === "object" || normalized.properties != null) {
|
|
190
|
+
normalized.properties ??= {};
|
|
191
|
+
normalized.properties = Object.fromEntries(Object.entries(normalized.properties).map(([name, property]) => [name, normalize(property)]));
|
|
192
|
+
normalized.additionalProperties = false;
|
|
193
|
+
}
|
|
194
|
+
if (normalized.$defs != null)
|
|
195
|
+
normalized.$defs = Object.fromEntries(Object.entries(normalized.$defs).map(([name, definition]) => [name, normalize(definition)]));
|
|
196
|
+
if (normalized.definitions != null)
|
|
197
|
+
normalized.definitions = Object.fromEntries(Object.entries(normalized.definitions).map(([name, definition]) => [name, normalize(definition)]));
|
|
198
|
+
if (normalized.items != null) {
|
|
199
|
+
normalized.items = Array.isArray(normalized.items) ? normalized.items.map(normalize) : normalize(normalized.items);
|
|
200
|
+
}
|
|
201
|
+
if (normalized.oneOf != null) {
|
|
202
|
+
const oneOf = normalized.oneOf;
|
|
203
|
+
delete normalized.oneOf;
|
|
204
|
+
normalized.anyOf = normalized.anyOf == null ? oneOf : normalized.anyOf.concat(oneOf);
|
|
205
|
+
}
|
|
206
|
+
if (normalized.anyOf != null)
|
|
207
|
+
normalized.anyOf = normalized.anyOf.map(normalize);
|
|
208
|
+
if (normalized.allOf != null)
|
|
209
|
+
normalized.allOf = normalized.allOf.map(normalize);
|
|
210
|
+
for (const key of ["exclusiveMaximum", "exclusiveMinimum", "maximum", "minimum", "multipleOf", "minLength", "maxLength", "maxItems"])
|
|
211
|
+
delete normalized[key];
|
|
212
|
+
if ((normalized.minItems ?? 0) > 1)
|
|
213
|
+
delete normalized.minItems;
|
|
214
|
+
if (normalized.format != null && !SUPPORTED_FORMATS.has(normalized.format))
|
|
215
|
+
delete normalized.format;
|
|
216
|
+
return normalized;
|
|
217
|
+
};
|
|
218
|
+
|
|
219
|
+
const normalizeFormat = (outputFormat) => outputFormat == null ? void 0 : {
|
|
220
|
+
schema: normalizeSchema(resolveSchema(outputFormat).schema),
|
|
221
|
+
type: "json_schema"
|
|
222
|
+
};
|
|
223
|
+
|
|
224
|
+
const DATA_URL_PATTERN = /^data:([^;,]+);base64,(.*)$/s;
|
|
225
|
+
const parseDataUrl = (data) => {
|
|
226
|
+
const match = DATA_URL_PATTERN.exec(data);
|
|
227
|
+
return match === null ? void 0 : { data: match[2], mediaType: match[1] };
|
|
228
|
+
};
|
|
229
|
+
const normalizeInputTextPart = (part) => ({
|
|
230
|
+
// TODO: Move citations to a first-class TextPart field.
|
|
231
|
+
citations: part.providerMetadata?.messages?.citations,
|
|
232
|
+
text: part.text,
|
|
233
|
+
type: "text"
|
|
234
|
+
});
|
|
235
|
+
const normalizeImagePart = (part) => {
|
|
236
|
+
const data = part.data.toString();
|
|
237
|
+
const dataUrl = parseDataUrl(data);
|
|
238
|
+
if (dataUrl != null)
|
|
239
|
+
return { source: { data: dataUrl.data, media_type: dataUrl.mediaType, type: "base64" }, type: "image" };
|
|
240
|
+
if (URL.canParse(data))
|
|
241
|
+
return { source: { type: "url", url: data }, type: "image" };
|
|
242
|
+
throw new XSAIError("invalid-input", "Image part data must be a URL or a base64 data: URL");
|
|
243
|
+
};
|
|
244
|
+
const normalizeFilePart = (part) => {
|
|
245
|
+
const data = part.data.toString();
|
|
246
|
+
const dataUrl = parseDataUrl(data);
|
|
247
|
+
if (dataUrl != null) {
|
|
248
|
+
if (dataUrl.mediaType !== "application/pdf")
|
|
249
|
+
throw new XSAIError("invalid-input", `Anthropic documents only support PDF or plain text, got: ${dataUrl.mediaType}`);
|
|
250
|
+
return { source: { data: dataUrl.data, media_type: "application/pdf", type: "base64" }, type: "document" };
|
|
251
|
+
}
|
|
252
|
+
if (URL.canParse(data))
|
|
253
|
+
return { source: { type: "url", url: data }, type: "document" };
|
|
254
|
+
return { source: { data, media_type: "text/plain", type: "text" }, type: "document" };
|
|
255
|
+
};
|
|
256
|
+
const normalizeToolResultContent = (content) => {
|
|
257
|
+
switch (content.type) {
|
|
258
|
+
case "image":
|
|
259
|
+
return normalizeImagePart(content);
|
|
260
|
+
case "text":
|
|
261
|
+
return normalizeInputTextPart(content);
|
|
262
|
+
}
|
|
263
|
+
};
|
|
264
|
+
const normalizeToolResultPart = (part) => ({
|
|
265
|
+
content: typeof part.output === "string" ? part.output : part.output.map(normalizeToolResultContent),
|
|
266
|
+
is_error: part.isError,
|
|
267
|
+
tool_use_id: part.callId,
|
|
268
|
+
type: "tool_result"
|
|
269
|
+
});
|
|
270
|
+
const normalizeReasoningPartContent = (content) => {
|
|
271
|
+
switch (content.type) {
|
|
272
|
+
case "encrypted":
|
|
273
|
+
case "redacted":
|
|
274
|
+
return [{ data: content.text, type: "redacted_thinking" }];
|
|
275
|
+
case "summary":
|
|
276
|
+
return [];
|
|
277
|
+
case "text":
|
|
278
|
+
return [{ thinking: content.text, type: "thinking" }];
|
|
279
|
+
}
|
|
280
|
+
};
|
|
281
|
+
const normalizeReasoningPart = (part) => {
|
|
282
|
+
const blocks = part.content.flatMap(normalizeReasoningPartContent);
|
|
283
|
+
const signature = part.providerMetadata?.messages?.signature;
|
|
284
|
+
if (signature == null)
|
|
285
|
+
return blocks;
|
|
286
|
+
const block = blocks.findLast((block2) => block2.type === "thinking");
|
|
287
|
+
if (block?.type === "thinking")
|
|
288
|
+
block.signature = signature;
|
|
289
|
+
return blocks;
|
|
290
|
+
};
|
|
291
|
+
const normalizeToolCallPart = (part) => ({
|
|
292
|
+
id: part.callId,
|
|
293
|
+
input: part.arguments === "" ? {} : JSON.parse(part.arguments),
|
|
294
|
+
name: part.name,
|
|
295
|
+
type: "tool_use"
|
|
296
|
+
});
|
|
297
|
+
const normalizeUserPart = (part) => {
|
|
298
|
+
switch (part.type) {
|
|
299
|
+
case "file":
|
|
300
|
+
return normalizeFilePart(part);
|
|
301
|
+
case "image":
|
|
302
|
+
return normalizeImagePart(part);
|
|
303
|
+
case "text":
|
|
304
|
+
return normalizeInputTextPart(part);
|
|
305
|
+
case "tool-result":
|
|
306
|
+
return normalizeToolResultPart(part);
|
|
307
|
+
}
|
|
308
|
+
};
|
|
309
|
+
const normalizeAssistantPart = (part, source) => {
|
|
310
|
+
switch (part.type) {
|
|
311
|
+
case "provider":
|
|
312
|
+
return part.source === source ? [part.value] : [];
|
|
313
|
+
case "reasoning":
|
|
314
|
+
return normalizeReasoningPart(part);
|
|
315
|
+
case "refusal":
|
|
316
|
+
throw new XSAIError("invalid-input", "Refusal parts cannot be replayed on the Messages API");
|
|
317
|
+
case "text":
|
|
318
|
+
return [normalizeInputTextPart(part)];
|
|
319
|
+
case "tool-call":
|
|
320
|
+
return [normalizeToolCallPart(part)];
|
|
321
|
+
}
|
|
322
|
+
};
|
|
323
|
+
const normalizeUserMessage = (message) => ({
|
|
324
|
+
content: typeof message.content === "string" ? [{ text: message.content, type: "text" }] : message.content.map(normalizeUserPart),
|
|
325
|
+
role: "user"
|
|
326
|
+
});
|
|
327
|
+
const normalizeAssistantMessage = (message, source) => {
|
|
328
|
+
const content = typeof message.content === "string" ? [{ text: message.content, type: "text" }] : message.content.flatMap((part) => normalizeAssistantPart(part, source));
|
|
329
|
+
return content.length === 0 ? void 0 : { content, role: "assistant" };
|
|
330
|
+
};
|
|
331
|
+
const systemText = (content) => typeof content === "string" ? [{ text: content, type: "text" }] : content.map(normalizeInputTextPart);
|
|
332
|
+
const normalizeInput = (options, source) => {
|
|
333
|
+
const system = [];
|
|
334
|
+
const messages = [];
|
|
335
|
+
if (options.instructions != null)
|
|
336
|
+
system.push({ text: options.instructions, type: "text" });
|
|
337
|
+
for (const message of typeof options.input === "string" ? [{ content: options.input, role: "user" }] : options.input) {
|
|
338
|
+
switch (message.role) {
|
|
339
|
+
case "assistant":
|
|
340
|
+
{
|
|
341
|
+
const assistant = normalizeAssistantMessage(message, source);
|
|
342
|
+
if (assistant != null)
|
|
343
|
+
messages.push(assistant);
|
|
344
|
+
}
|
|
345
|
+
break;
|
|
346
|
+
case "developer":
|
|
347
|
+
case "system":
|
|
348
|
+
system.push(...systemText(message.content));
|
|
349
|
+
break;
|
|
350
|
+
case "user":
|
|
351
|
+
messages.push(normalizeUserMessage(message));
|
|
352
|
+
break;
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
return {
|
|
356
|
+
messages,
|
|
357
|
+
system: system.length === 0 ? void 0 : system
|
|
358
|
+
};
|
|
359
|
+
};
|
|
360
|
+
|
|
361
|
+
const normalizeTools = (tools) => tools?.map((tool) => ({
|
|
362
|
+
description: tool.description,
|
|
363
|
+
input_schema: normalizeSchema(tool.inputSchema.schema),
|
|
364
|
+
name: tool.name
|
|
365
|
+
}));
|
|
366
|
+
const mergeTools = (providerTools, tools) => {
|
|
367
|
+
const merged = [
|
|
368
|
+
...providerTools ?? [],
|
|
369
|
+
...normalizeTools(tools) ?? []
|
|
370
|
+
];
|
|
371
|
+
return merged.length === 0 ? void 0 : merged;
|
|
372
|
+
};
|
|
373
|
+
const normalizeToolChoice = (toolChoice) => toolChoice == null ? void 0 : typeof toolChoice === "string" ? { type: toolChoice === "required" ? "any" : toolChoice } : { name: toolChoice.name, type: "tool" };
|
|
374
|
+
|
|
375
|
+
const ANTHROPIC_VERSION = "2023-06-01";
|
|
376
|
+
const messages = (options) => async (modelOptions) => {
|
|
377
|
+
const source = requestURL("messages", options.baseURL).toString();
|
|
378
|
+
const { messages: inputMessages, system } = normalizeInput(modelOptions, source);
|
|
379
|
+
const maxTokens = modelOptions.maxOutputTokens;
|
|
380
|
+
const outputFormat = normalizeFormat(modelOptions.outputFormat);
|
|
381
|
+
const providerOptions = modelOptions.providerOptions?.messages;
|
|
382
|
+
if (typeof maxTokens !== "number")
|
|
383
|
+
throw new XSAIError("invalid-input", "maxOutputTokens is required for the Messages API");
|
|
384
|
+
return wireRequest({
|
|
385
|
+
body: {
|
|
386
|
+
cache_control: providerOptions?.cacheControl,
|
|
387
|
+
max_tokens: maxTokens,
|
|
388
|
+
mcp_servers: providerOptions?.mcpServers,
|
|
389
|
+
messages: inputMessages,
|
|
390
|
+
model: options.model,
|
|
391
|
+
output_config: modelOptions.reasoningEffort == null && outputFormat == null ? void 0 : { effort: modelOptions.reasoningEffort, format: outputFormat },
|
|
392
|
+
stop_sequences: providerOptions?.stopSequences,
|
|
393
|
+
stream: true,
|
|
394
|
+
system,
|
|
395
|
+
temperature: modelOptions.temperature,
|
|
396
|
+
thinking: providerOptions?.thinking,
|
|
397
|
+
tool_choice: normalizeToolChoice(modelOptions.toolChoice),
|
|
398
|
+
tools: mergeTools(providerOptions?.tools, modelOptions.tools),
|
|
399
|
+
top_p: modelOptions.topP
|
|
400
|
+
},
|
|
401
|
+
headers: {
|
|
402
|
+
...options.headers,
|
|
403
|
+
"anthropic-beta": providerOptions?.betas !== void 0 && providerOptions.betas.length > 0 ? providerOptions.betas.join(",") : options.headers?.["anthropic-beta"],
|
|
404
|
+
"anthropic-version": ANTHROPIC_VERSION,
|
|
405
|
+
"Content-Type": "application/json",
|
|
406
|
+
"x-api-key": options.apiKey ?? options.headers?.["x-api-key"]
|
|
407
|
+
},
|
|
408
|
+
path: source
|
|
409
|
+
}, options, modelOptions, new MessagesEventStream(source, modelOptions.includeRawEvents));
|
|
410
|
+
};
|
|
411
|
+
|
|
412
|
+
export { messages };
|
package/package.json
CHANGED
|
@@ -1,6 +1,49 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@xsai/text-messages",
|
|
3
|
-
"
|
|
4
|
-
"
|
|
5
|
-
"description": "
|
|
3
|
+
"type": "module",
|
|
4
|
+
"version": "1.0.0-beta.1",
|
|
5
|
+
"description": "extra-small AI SDK: Anthropic Messages API model for @xsai/text.",
|
|
6
|
+
"author": "Moeru AI",
|
|
7
|
+
"license": "MIT",
|
|
8
|
+
"homepage": "https://xsai.js.org",
|
|
9
|
+
"repository": {
|
|
10
|
+
"type": "git",
|
|
11
|
+
"url": "git+https://github.com/moeru-ai/xsai.git",
|
|
12
|
+
"directory": "packages/text-messages"
|
|
13
|
+
},
|
|
14
|
+
"bugs": "https://github.com/moeru-ai/xsai/issues",
|
|
15
|
+
"keywords": [
|
|
16
|
+
"xsai",
|
|
17
|
+
"ai",
|
|
18
|
+
"anthropic",
|
|
19
|
+
"messages",
|
|
20
|
+
"claude"
|
|
21
|
+
],
|
|
22
|
+
"sideEffects": false,
|
|
23
|
+
"exports": {
|
|
24
|
+
".": {
|
|
25
|
+
"types": "./dist/index.d.ts",
|
|
26
|
+
"default": "./dist/index.js"
|
|
27
|
+
},
|
|
28
|
+
"./package.json": "./package.json"
|
|
29
|
+
},
|
|
30
|
+
"files": [
|
|
31
|
+
"dist"
|
|
32
|
+
],
|
|
33
|
+
"peerDependencies": {
|
|
34
|
+
"@xsai/text": "1.0.0-beta.1"
|
|
35
|
+
},
|
|
36
|
+
"dependencies": {
|
|
37
|
+
"@xsai/shared": "1.0.0-beta.1"
|
|
38
|
+
},
|
|
39
|
+
"devDependencies": {
|
|
40
|
+
"@xsai/text": "1.0.0-beta.1"
|
|
41
|
+
},
|
|
42
|
+
"scripts": {
|
|
43
|
+
"build": "pkgroll --clean-dist",
|
|
44
|
+
"test": "vitest run --exclude e2e/**",
|
|
45
|
+
"test:watch": "vitest"
|
|
46
|
+
},
|
|
47
|
+
"main": "./dist/index.js",
|
|
48
|
+
"types": "./dist/index.d.ts"
|
|
6
49
|
}
|