@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/package.json CHANGED
@@ -1,5 +1,6 @@
1
1
  {
2
2
  "name": "@codehz/ai",
3
+ "version": "0.1.5",
3
4
  "type": "module",
4
5
  "module": "dist/index.mjs",
5
6
  "exports": {
@@ -26,6 +27,5 @@
26
27
  "oxlint": "^1.73.0",
27
28
  "tsdown": "^0.22.3",
28
29
  "typescript": "^6"
29
- },
30
- "version": "0.1.3"
30
+ }
31
31
  }
@@ -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 = messages.length > 0 && messages[messages.length - 1]?.role === "assistant"
297
- ? messages[messages.length - 1]
298
- : null;
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",
@@ -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
- MockTurnContext,
29
- MockTurnValidator,
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";
@@ -105,7 +105,7 @@ function ensureMessagesReasoningBlocks(
105
105
  });
106
106
  }
107
107
 
108
- function instructionsToMessagesText(instructions: string | import("../index.js").ContentBlock[]): string {
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" || b.type === "thinking" || b.type === "redacted_thinking" || b.type === "tool_use" || b.type === "tool_result"),
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("Request metadata is not supported by the Messages adapter", "UNSUPPORTED_METADATA");
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: string | undefined;
454
+ let rawResponseId = "";
453
455
 
454
456
  if (request.include?.providerMetadata !== "off") {
455
457
  const headerMetadata = pickProviderHeaders(response.headers);
456
- auxiliary.recordProviderMetadata("header", Object.keys(headerMetadata).length > 0 ? { headers: headerMetadata } : undefined);
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 = `msg-${block.type}-${currentContentBlockIndex}`;
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 = `reason-${currentContentBlockIndex}`;
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 = `reason-redacted-${currentContentBlockIndex}`;
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
- inputTokens: u.input_tokens,
617
- outputTokens: u.output_tokens,
618
- totalTokens: u.input_tokens + u.output_tokens,
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,