@codehz/ai 0.2.3 → 0.3.0

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.
@@ -1,18 +1,10 @@
1
1
  import { AIRequestError } from "../core/errors.js";
2
2
  import { contentBlocksToText } from "./mapping.js";
3
3
 
4
- import type { AdapterCapabilities, ContentBlock, InstructionBlock, ToolResultItem } from "../types/index.js";
5
-
6
- export type ProviderProfile = {
7
- readonly kind: string;
8
- readonly instructionsMode: "system_message" | "instructions_field" | "none";
9
- readonly supportedBlockTypes: ReadonlyArray<ContentBlock["type"]>;
10
- readonly reasoningBlockTypes: ReadonlyArray<ContentBlock["type"]>;
11
- readonly capabilities: AdapterCapabilities;
12
- };
4
+ import type { ContentBlock, InstructionBlock, ToolCallItem } from "../types/index.js";
13
5
 
14
6
  export class NormalizedRequestMapper {
15
- constructor(readonly profile: ProviderProfile) {}
7
+ constructor(readonly kind: string) {}
16
8
 
17
9
  mapInstructions(instructions: string | InstructionBlock[]): string {
18
10
  return typeof instructions === "string"
@@ -21,27 +13,28 @@ export class NormalizedRequestMapper {
21
13
  }
22
14
 
23
15
  ensureTextBlocks(blocks: ContentBlock[], field: string): ContentBlock[] {
24
- return this.ensureBlocks(blocks, field, this.profile.supportedBlockTypes, "only text/json blocks are supported");
16
+ return this.ensureBlocks(blocks, field, ["text", "json"], "only text/json blocks are supported");
25
17
  }
26
18
 
27
19
  ensureReasoningBlocks(blocks: ContentBlock[], field: string): Array<Extract<ContentBlock, { type: "text" }>> {
28
- return this.ensureBlocks(
29
- blocks,
30
- field,
31
- this.profile.reasoningBlockTypes,
32
- "reasoning only supports text blocks",
33
- ) as Array<Extract<ContentBlock, { type: "text" }>>;
20
+ return this.ensureBlocks(blocks, field, ["text"], "reasoning only supports text blocks") as Array<
21
+ Extract<ContentBlock, { type: "text" }>
22
+ >;
34
23
  }
35
24
 
36
- assertToolResultOutcome(outcome: ToolResultItem["outcome"]): void {
37
- if (this.profile.capabilities.toolResultOutcomes.includes(outcome)) return;
25
+ parseToolArguments(item: ToolCallItem): Record<string, unknown> {
26
+ try {
27
+ const parsed: unknown = JSON.parse(item.argumentsText);
28
+ if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
29
+ return parsed as Record<string, unknown>;
30
+ }
31
+ } catch {
32
+ // handled below
33
+ }
38
34
 
39
- const outcomes = this.profile.capabilities.toolResultOutcomes;
40
- const supported = outcomes.map((value) => `"${value}"`).join(" and ");
41
- const verb = outcomes.length > 1 ? "are" : "is";
42
35
  throw new AIRequestError(
43
- `${this.profile.kind} does not preserve tool_result outcome "${outcome}"; only ${supported} ${verb} supported`,
44
- "UNSUPPORTED_TOOL_RESULT_OUTCOME",
36
+ `${this.kind} requires tool_call argumentsText to be a valid JSON object`,
37
+ "TOOL_CALL_ARGUMENTS_INVALID",
45
38
  );
46
39
  }
47
40
 
@@ -61,7 +54,7 @@ export class NormalizedRequestMapper {
61
54
  const block = blocks[i];
62
55
  if (block && !supportedTypes.includes(block.type)) {
63
56
  throw new AIRequestError(
64
- `${this.profile.kind} does not support ${field}[${i}] of type "${block.type}"; ${description}`,
57
+ `${this.kind} does not support ${field}[${i}] of type "${block.type}"; ${description}`,
65
58
  "UNSUPPORTED_CONTENT_BLOCK",
66
59
  );
67
60
  }
package/src/index.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  /**
2
- * nano-ai — 统一流式 AI 客户端
2
+ * @codehz/ai — 统一流式 AI 客户端
3
3
  *
4
4
  * 对外只暴露一个 canonical 主入口:client.stream()
5
5
  */
@@ -21,20 +21,9 @@ export type NormalizedRequest = AIRequest & {
21
21
 
22
22
  // ── Adapter 接口 ──────────────────────────────────────────────
23
23
 
24
- export type StreamingCapability = "native" | "synthetic" | "none";
25
-
26
- export type AdapterCapabilities = {
27
- readonly textStreaming: StreamingCapability;
28
- readonly reasoningStreaming: StreamingCapability;
29
- readonly toolCallStreaming: StreamingCapability;
30
- readonly replay: "canonical" | "opaque" | "none";
31
- readonly usage: "stream" | "final" | "none";
32
- readonly toolResultOutcomes: ReadonlyArray<"success" | "error" | "rejected">;
33
- };
34
-
35
24
  export interface BackendAdapter {
36
25
  readonly kind: "chat-completions" | "messages" | "responses" | "ollama" | "mock";
37
- readonly capabilities: AdapterCapabilities;
26
+ readonly isSyntheticStream: boolean;
38
27
  stream(request: NormalizedRequest): AsyncIterable<AIStreamEvent>;
39
28
  }
40
29
 
@@ -44,6 +33,8 @@ export type CreateAIClientOptions = {
44
33
  adapter: BackendAdapter;
45
34
  model: string;
46
35
  defaults?: Partial<AIRequest>;
36
+ /** 全局默认 AbortSignal,当 request.signal 未设置时生效。 */
37
+ signal?: AbortSignal;
47
38
  };
48
39
 
49
40
  export interface AIClient {
@@ -46,12 +46,4 @@ export type {
46
46
  } from "./events.js";
47
47
 
48
48
  // Adapter 协议和 client 类型
49
- export type {
50
- AdapterCapabilities,
51
- StreamingCapability,
52
- BackendAdapter,
53
- FetchFn,
54
- NormalizedRequest,
55
- CreateAIClientOptions,
56
- AIClient,
57
- } from "./adapter.js";
49
+ export type { BackendAdapter, FetchFn, NormalizedRequest, CreateAIClientOptions, AIClient } from "./adapter.js";
@@ -27,7 +27,6 @@ export type ToolCallItem = {
27
27
  id: string;
28
28
  name: string;
29
29
  argumentsText: string;
30
- argumentsJson?: unknown;
31
30
  };
32
31
 
33
32
  export type ToolResultItem = {
@@ -36,4 +36,6 @@ export type AIRequest = {
36
36
  metadata?: Record<string, string>;
37
37
  temperature?: number;
38
38
  maxOutputTokens?: number;
39
+ /** AbortSignal 用于打断请求。abort 时 fetch 调用会被取消,流迭代器抛出 AbortError。 */
40
+ signal?: AbortSignal;
39
41
  };