@codehz/ai 0.1.3 → 0.1.5
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 +86 -81
- package/dist/index.d.mts +29 -34
- package/dist/index.mjs +140 -123
- package/dist/index.mjs.map +1 -1
- package/package.json +2 -2
- package/src/adapters/chat-completions.ts +5 -11
- package/src/adapters/index.ts +5 -3
- package/src/adapters/messages.ts +27 -22
- package/src/adapters/mock.ts +190 -160
- package/src/adapters/ollama.ts +32 -27
- package/src/adapters/responses.ts +6 -8
- package/src/core/validation.ts +41 -18
- package/src/helpers/adapter-auxiliary.ts +3 -1
- package/src/helpers/adapter-base.ts +5 -5
- package/src/helpers/index.ts +1 -5
- package/src/helpers/mapping.ts +3 -2
- package/src/types/content.ts +5 -2
- package/src/types/index.ts +2 -8
- package/src/types/items.ts +1 -1
- package/src/types/request.ts +2 -2
- package/bun.lock +0 -231
package/package.json
CHANGED
|
@@ -279,23 +279,17 @@ export class ChatCompletionsAdapter extends AdapterBase {
|
|
|
279
279
|
for (const item of request.input) {
|
|
280
280
|
switch (item.type) {
|
|
281
281
|
case "message": {
|
|
282
|
-
const role =
|
|
283
|
-
item.role === "developer"
|
|
284
|
-
? "system"
|
|
285
|
-
: item.role === "system"
|
|
286
|
-
? "system"
|
|
287
|
-
: item.role === "user"
|
|
288
|
-
? "user"
|
|
289
|
-
: "assistant";
|
|
282
|
+
const role = item.role;
|
|
290
283
|
const text = contentBlocksToChatText(item.content, `input message (${item.role}) content`);
|
|
291
284
|
messages.push({ role, content: text || null });
|
|
292
285
|
break;
|
|
293
286
|
}
|
|
294
287
|
case "tool_call": {
|
|
295
288
|
// 只允许附着到尾部 assistant turn,否则新建一个
|
|
296
|
-
const lastAssistant =
|
|
297
|
-
|
|
298
|
-
|
|
289
|
+
const lastAssistant =
|
|
290
|
+
messages.length > 0 && messages[messages.length - 1]?.role === "assistant"
|
|
291
|
+
? messages[messages.length - 1]
|
|
292
|
+
: null;
|
|
299
293
|
const tc: ChatToolCall = {
|
|
300
294
|
id: item.id,
|
|
301
295
|
type: "function",
|
package/src/adapters/index.ts
CHANGED
|
@@ -22,11 +22,13 @@ export type { OllamaAdapterOptions } from "./ollama.js";
|
|
|
22
22
|
export { MockAdapter } from "./mock.js";
|
|
23
23
|
export type {
|
|
24
24
|
MockAdapterOptions,
|
|
25
|
+
MockHistoryRecord,
|
|
25
26
|
MockTextStreamOptions,
|
|
26
27
|
MockInputExpectation,
|
|
27
28
|
MockRequestExpectation,
|
|
28
|
-
|
|
29
|
-
|
|
29
|
+
MockHandlerContext,
|
|
30
|
+
MockHandler,
|
|
31
|
+
MockStaticHandler,
|
|
30
32
|
MockWarningStep,
|
|
31
33
|
MockAuxiliaryStep,
|
|
32
34
|
MockMessageStep,
|
|
@@ -38,5 +40,5 @@ export type {
|
|
|
38
40
|
MockInterruptStep,
|
|
39
41
|
MockThrowStep,
|
|
40
42
|
MockStep,
|
|
41
|
-
MockTurn,
|
|
42
43
|
} from "./mock.js";
|
|
44
|
+
export { assertMockRequest, withMockStreaming } from "./mock.js";
|
package/src/adapters/messages.ts
CHANGED
|
@@ -105,7 +105,7 @@ function ensureMessagesReasoningBlocks(
|
|
|
105
105
|
});
|
|
106
106
|
}
|
|
107
107
|
|
|
108
|
-
function instructionsToMessagesText(instructions: string | import("../index.js").
|
|
108
|
+
function instructionsToMessagesText(instructions: string | import("../index.js").InstructionBlock[]): string {
|
|
109
109
|
return typeof instructions === "string"
|
|
110
110
|
? instructions
|
|
111
111
|
: contentBlocksToText(ensureMessagesTextBlocks(instructions, "instructions"));
|
|
@@ -162,6 +162,11 @@ function rollbackTrailingAssistantMessages(messages: MessagesAPIMessage[]): void
|
|
|
162
162
|
}
|
|
163
163
|
}
|
|
164
164
|
|
|
165
|
+
/** 用 response 级别的命名空间合成 content block 的 item ID,避免多轮工具循环 ID 碰撞 */
|
|
166
|
+
function synthesizeItemId(kind: "msg" | "reason" | "reason-redacted", blockIndex: number, responseId: string): string {
|
|
167
|
+
return `${kind}-${blockIndex}-${responseId}`;
|
|
168
|
+
}
|
|
169
|
+
|
|
165
170
|
function parseToolUseInput(input: string): Record<string, unknown> {
|
|
166
171
|
try {
|
|
167
172
|
const parsed = JSON.parse(input);
|
|
@@ -272,14 +277,6 @@ export class MessagesAdapter extends AdapterBase {
|
|
|
272
277
|
for (const item of request.input) {
|
|
273
278
|
switch (item.type) {
|
|
274
279
|
case "message": {
|
|
275
|
-
if (item.role === "system" || item.role === "developer") {
|
|
276
|
-
// Anthropic 不支持 system/developer role 在 messages 中
|
|
277
|
-
// 合并到 system prompt
|
|
278
|
-
const text = contentBlocksToText(ensureMessagesTextBlocks(item.content, `input message (${item.role}) content`));
|
|
279
|
-
systemPrompt = systemPrompt ? `${systemPrompt}\n${text}` : text;
|
|
280
|
-
break;
|
|
281
|
-
}
|
|
282
|
-
|
|
283
280
|
const role = item.role === "user" ? "user" : "assistant";
|
|
284
281
|
const supportedContent = ensureMessagesTextBlocks(item.content, `input message (${item.role}) content`);
|
|
285
282
|
if (supportedContent.length === 1 && supportedContent[0]?.type === "text") {
|
|
@@ -296,9 +293,7 @@ export class MessagesAdapter extends AdapterBase {
|
|
|
296
293
|
type: "tool_use",
|
|
297
294
|
id: item.id,
|
|
298
295
|
name: item.name,
|
|
299
|
-
input:
|
|
300
|
-
(item.argumentsJson as Record<string, unknown> | undefined) ??
|
|
301
|
-
parseToolUseInput(item.argumentsText),
|
|
296
|
+
input: (item.argumentsJson as Record<string, unknown> | undefined) ?? parseToolUseInput(item.argumentsText),
|
|
302
297
|
};
|
|
303
298
|
|
|
304
299
|
if (lastMsg && lastMsg.role === "assistant" && typeof lastMsg.content !== "string") {
|
|
@@ -345,7 +340,11 @@ export class MessagesAdapter extends AdapterBase {
|
|
|
345
340
|
typeof b === "object" &&
|
|
346
341
|
b !== null &&
|
|
347
342
|
"type" in b &&
|
|
348
|
-
(b.type === "text" ||
|
|
343
|
+
(b.type === "text" ||
|
|
344
|
+
b.type === "thinking" ||
|
|
345
|
+
b.type === "redacted_thinking" ||
|
|
346
|
+
b.type === "tool_use" ||
|
|
347
|
+
b.type === "tool_result"),
|
|
349
348
|
);
|
|
350
349
|
if (isValidContent) {
|
|
351
350
|
rollbackTrailingAssistantMessages(messages);
|
|
@@ -404,7 +403,10 @@ export class MessagesAdapter extends AdapterBase {
|
|
|
404
403
|
const auxiliary = this.createAuxiliaryState(request);
|
|
405
404
|
|
|
406
405
|
if (request.metadata) {
|
|
407
|
-
yield factory.responseWarning(
|
|
406
|
+
yield factory.responseWarning(
|
|
407
|
+
"Request metadata is not supported by the Messages adapter",
|
|
408
|
+
"UNSUPPORTED_METADATA",
|
|
409
|
+
);
|
|
408
410
|
}
|
|
409
411
|
|
|
410
412
|
const response = await this.fetchFn(`${this.baseUrl}/messages`, {
|
|
@@ -449,11 +451,14 @@ export class MessagesAdapter extends AdapterBase {
|
|
|
449
451
|
// 完成响应数据
|
|
450
452
|
let stopReason: string | undefined;
|
|
451
453
|
let stopSequence: string | null | undefined;
|
|
452
|
-
let rawResponseId
|
|
454
|
+
let rawResponseId = "";
|
|
453
455
|
|
|
454
456
|
if (request.include?.providerMetadata !== "off") {
|
|
455
457
|
const headerMetadata = pickProviderHeaders(response.headers);
|
|
456
|
-
auxiliary.recordProviderMetadata(
|
|
458
|
+
auxiliary.recordProviderMetadata(
|
|
459
|
+
"header",
|
|
460
|
+
Object.keys(headerMetadata).length > 0 ? { headers: headerMetadata } : undefined,
|
|
461
|
+
);
|
|
457
462
|
}
|
|
458
463
|
|
|
459
464
|
try {
|
|
@@ -503,7 +508,7 @@ export class MessagesAdapter extends AdapterBase {
|
|
|
503
508
|
switch (block.type) {
|
|
504
509
|
case "text": {
|
|
505
510
|
currentItemType = "message";
|
|
506
|
-
currentItemId =
|
|
511
|
+
currentItemId = synthesizeItemId("msg", currentContentBlockIndex, rawResponseId);
|
|
507
512
|
textBuffer = "";
|
|
508
513
|
yield factory.messageStarted(currentItemId);
|
|
509
514
|
break;
|
|
@@ -511,7 +516,7 @@ export class MessagesAdapter extends AdapterBase {
|
|
|
511
516
|
case "thinking": {
|
|
512
517
|
hasStreamedReasoning = true;
|
|
513
518
|
currentItemType = "reasoning";
|
|
514
|
-
currentItemId =
|
|
519
|
+
currentItemId = synthesizeItemId("reason", currentContentBlockIndex, rawResponseId);
|
|
515
520
|
currentThinkingVisibility = "full";
|
|
516
521
|
thinkingBuffer = "";
|
|
517
522
|
yield factory.reasoningStarted(currentItemId, "full");
|
|
@@ -520,7 +525,7 @@ export class MessagesAdapter extends AdapterBase {
|
|
|
520
525
|
case "redacted_thinking": {
|
|
521
526
|
hasStreamedReasoning = true;
|
|
522
527
|
currentItemType = "reasoning";
|
|
523
|
-
currentItemId =
|
|
528
|
+
currentItemId = synthesizeItemId("reason-redacted", currentContentBlockIndex, rawResponseId);
|
|
524
529
|
currentThinkingVisibility = "redacted";
|
|
525
530
|
const data = (block as unknown as { data: string }).data;
|
|
526
531
|
yield factory.reasoningStarted(currentItemId, "redacted");
|
|
@@ -613,9 +618,9 @@ export class MessagesAdapter extends AdapterBase {
|
|
|
613
618
|
if (u) {
|
|
614
619
|
auxiliary.recordUsage(
|
|
615
620
|
{
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
621
|
+
inputTokens: u.input_tokens,
|
|
622
|
+
outputTokens: u.output_tokens,
|
|
623
|
+
totalTokens: u.input_tokens + u.output_tokens,
|
|
619
624
|
},
|
|
620
625
|
"stream",
|
|
621
626
|
u,
|