@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 CHANGED
@@ -1,6 +1,6 @@
1
1
  # nano-ai
2
2
 
3
- 统一流式 AI 客户端,提供一套 canonical API,对接真实模型后端与面向测试的脚本化 `MockAdapter`(`responses` / `messages` / `chat.completions` / `ollama` / `mock`)。
3
+ 统一流式 AI 客户端,提供一套 canonical API,对接真实模型后端与面向测试的回调驱动 `MockAdapter`(`responses` / `messages` / `chat.completions` / `ollama` / `mock`)。
4
4
 
5
5
  ## 安装
6
6
 
@@ -39,7 +39,7 @@ for await (const event of stream) {
39
39
 
40
40
  ```ts
41
41
  type AIRequest = {
42
- instructions?: string; // 系统指令
42
+ instructions?: string | InstructionBlock[]; // 系统级指令
43
43
  input: InputItem[]; // 输入 items
44
44
  tools?: ToolDefinition[]; // 工具声明
45
45
  toolChoice?: ToolChoice; // 工具选择策略
@@ -53,7 +53,7 @@ type AIRequest = {
53
53
 
54
54
  | Item 类型 | 用途 |
55
55
  | ------------- | ----------------------------------- |
56
- | `message` | 用户 / 助手 / 系统消息 |
56
+ | `message` | 用户 / 助手消息 |
57
57
  | `reasoning` | 思维链(输入侧 replay) |
58
58
  | `tool_call` | 模型发起的工具调用(输入侧 replay) |
59
59
  | `tool_result` | 工具执行结果 |
@@ -109,16 +109,23 @@ console.log(response.replay); // 续接材料
109
109
 
110
110
  ## 后端 Adapter
111
111
 
112
- | Adapter | 类 | 说明 |
113
- | ----------------------- | ------------------------ | ------------------------ |
114
- | OpenAI Responses API | `ResponsesAdapter` | OpenAI Responses 端点 |
115
- | Anthropic Messages API | `MessagesAdapter` | Anthropic Messages 端点 |
116
- | OpenAI Chat Completions | `ChatCompletionsAdapter` | Chat Completions 端点 |
117
- | Ollama Chat API | `OllamaAdapter` | 本地或自托管 Ollama |
118
- | Scripted Test Backend | `MockAdapter` | 脚本化测试夹具 |
112
+ | Adapter | 类 | 说明 |
113
+ | ----------------------- | ------------------------ | ----------------------- |
114
+ | OpenAI Responses API | `ResponsesAdapter` | OpenAI Responses 端点 |
115
+ | Anthropic Messages API | `MessagesAdapter` | Anthropic Messages 端点 |
116
+ | OpenAI Chat Completions | `ChatCompletionsAdapter` | Chat Completions 端点 |
117
+ | Ollama Chat API | `OllamaAdapter` | 本地或自托管 Ollama |
118
+ | Scripted Test Backend | `MockAdapter` | 脚本化测试夹具 |
119
119
 
120
120
  ```ts
121
- import { ResponsesAdapter, MessagesAdapter, ChatCompletionsAdapter, OllamaAdapter, MockAdapter } from "nano-ai";
121
+ import {
122
+ ResponsesAdapter,
123
+ MessagesAdapter,
124
+ ChatCompletionsAdapter,
125
+ OllamaAdapter,
126
+ MockAdapter,
127
+ withMockStreaming,
128
+ } from "nano-ai";
122
129
 
123
130
  // OpenAI Responses API
124
131
  const responses = new ResponsesAdapter({ apiKey: "sk-..." });
@@ -132,25 +139,23 @@ const chat = new ChatCompletionsAdapter({ apiKey: "sk-..." });
132
139
  // Ollama
133
140
  const ollama = new OllamaAdapter({ baseUrl: "http://localhost:11434" });
134
141
 
135
- // 面向测试的脚本化 mock backend
142
+ // 面向测试的回调驱动 mock backend
136
143
  const mock = new MockAdapter({
137
- stream: {
138
- charsPerSecond: 24,
139
- chunkSize: 1,
140
- },
141
- turns: [
144
+ handler: withMockStreaming(
145
+ async function* () {
146
+ yield { type: "message", content: "我先调用天气工具。" };
147
+ yield {
148
+ type: "tool_call",
149
+ id: "mock-call-weather",
150
+ name: "get_weather",
151
+ argumentsText: '{"city":"Hangzhou"}',
152
+ };
153
+ },
142
154
  {
143
- steps: [
144
- { type: "message", content: "我先调用天气工具。" },
145
- {
146
- type: "tool_call",
147
- id: "mock-call-weather",
148
- name: "get_weather",
149
- argumentsText: '{"city":"Hangzhou"}',
150
- },
151
- ],
155
+ charsPerSecond: 24,
156
+ chunkSize: 1,
152
157
  },
153
- ],
158
+ ),
154
159
  });
155
160
  ```
156
161
 
@@ -165,66 +170,71 @@ adapter.nativeStreaming; // 是否为 provider 原生流,而不是本地模拟
165
170
 
166
171
  ## Mock 后端
167
172
 
168
- `MockAdapter` 是一个面向测试的脚本化 adapter,用来验证长流程工具调用、`replay` 续接和异常路径。
173
+ `MockAdapter` 是一个面向测试的回调驱动 adapter,用来验证长流程工具调用、`replay` 续接和异常路径。
169
174
 
170
- 如果你要调试前端逐字渲染效果,可以给 `MockAdapter` 打开分片流:
175
+ 如果你要调试前端逐字渲染效果,可以用 `withMockStreaming()` 给非流式 handler 包一层分片输出:
171
176
 
172
177
  ```ts
173
- const mock = new MockAdapter({
174
- stream: {
178
+ const handler = withMockStreaming(
179
+ async function* () {
180
+ yield { type: "message", content: "Streaming preview for the frontend." };
181
+ },
182
+ {
175
183
  charsPerSecond: 20, // 每秒约 20 个字符
176
184
  chunkSize: 1, // 默认 1,即逐字输出
177
185
  initialDelayMs: 150, // 可选:首字前停顿
178
186
  },
179
- turns: [
180
- {
181
- steps: [{ type: "message", content: "Streaming preview for the frontend." }],
182
- },
183
- ],
184
- });
187
+ );
185
188
  ```
186
189
 
187
- 默认会发出单个完整 `message.delta`。只有显式配置 `stream` 时,`message` / `reasoning` / `tool_call` 参数才会被拆成多个 delta。单个 step 也可用 `stream: false` 关闭全局流速配置。
190
+ 默认会发出单个完整 `message.delta`。只有经 `withMockStreaming()` 注入默认流速后,`message` / `reasoning` / `tool_call` 参数才会被拆成多个 delta。单个 step 也可用 `stream: false` 关闭包装器的默认流速配置。
188
191
 
189
- 核心思路是按 turn 写脚本:
192
+ 核心思路是每轮请求执行一次 handler:
190
193
 
191
- - 每一轮可声明对请求格式的期望
192
- - 每一轮可脚本化发出 `message` / `reasoning` / `tool_call`
194
+ - handler 会拿到 `request` 和 `context`
195
+ - `context` 内建 `previousReplay`、`pendingToolCalls`、`history`
196
+ - handler 可脚本化发出 `message` / `reasoning` / `tool_call`
193
197
  - 可注入 `warning`、`content_filter`、transport interruption、provider-style error
194
- - 可验证调用方是否把上一轮 `replay` 和当前 `tool_result` 正确带回
198
+ - 可用 `assertMockRequest()` 验证调用方是否把上一轮 `replay` 和当前 `tool_result` 正确带回
195
199
 
196
200
  ```ts
197
- import { createAIClient, MockAdapter } from "nano-ai";
201
+ import { assertMockRequest, createAIClient, MockAdapter } from "nano-ai";
198
202
 
199
203
  const client = createAIClient({
200
204
  adapter: new MockAdapter({
201
- turns: [
202
- {
203
- name: "request-tool",
204
- expect: {
205
- items: [{ type: "message", role: "user", textIncludes: "weather" }],
206
- tools: "present",
207
- toolChoice: "present",
208
- },
209
- steps: [
210
- { type: "message", content: "Checking weather now." },
205
+ handler: async function* (request, context) {
206
+ if (context.turnIndex === 0) {
207
+ assertMockRequest(
208
+ request,
211
209
  {
212
- type: "tool_call",
213
- id: "mock-call-weather",
214
- name: "get_weather",
215
- argumentsText: '{"city":"Hangzhou"}',
210
+ items: [{ type: "message", role: "user", textIncludes: "weather" }],
211
+ tools: "present",
212
+ toolChoice: "present",
216
213
  },
217
- ],
218
- },
219
- {
220
- name: "consume-tool-result",
221
- expect: {
214
+ context,
215
+ );
216
+
217
+ yield { type: "message", content: "Checking weather now." };
218
+ yield {
219
+ type: "tool_call",
220
+ id: "mock-call-weather",
221
+ name: "get_weather",
222
+ argumentsText: '{"city":"Hangzhou"}',
223
+ };
224
+ return;
225
+ }
226
+
227
+ assertMockRequest(
228
+ request,
229
+ {
222
230
  requireReplayFromPreviousTurn: true,
223
231
  requireToolResultsForPendingCalls: true,
224
232
  },
225
- steps: [{ type: "message", content: "Hangzhou is 28C and sunny." }],
226
- },
227
- ],
233
+ context,
234
+ );
235
+
236
+ yield { type: "message", content: "Hangzhou is 28C and sunny." };
237
+ },
228
238
  }),
229
239
  model: "mock-model",
230
240
  });
@@ -233,10 +243,13 @@ const client = createAIClient({
233
243
  核心类型:
234
244
 
235
245
  ```ts
236
- type MockTurn = {
237
- name?: string;
238
- expect?: MockRequestExpectation | MockTurnValidator;
239
- steps: MockStep[];
246
+ type MockHandler = (request: NormalizedRequest, context: MockHandlerContext) => AsyncIterable<MockStep>;
247
+
248
+ type MockHandlerContext = {
249
+ turnIndex: number;
250
+ previousReplay: ReplayItem[];
251
+ pendingToolCalls: readonly ToolCallItem[];
252
+ history: readonly MockHistoryRecord[];
240
253
  };
241
254
  ```
242
255
 
@@ -248,21 +261,13 @@ type MockTurn = {
248
261
  畸形路径示例:
249
262
 
250
263
  ```ts
251
- {
252
- steps: [
253
- { type: "message", content: "partial answer" },
254
- { type: "interrupt" }, // 不发 response.completed,collectStream() 应失败
255
- ],
256
- }
264
+ yield { type: "message", content: "partial answer" };
265
+ yield { type: "interrupt" }; // 不发 response.completed,collectStream() 应失败
257
266
  ```
258
267
 
259
268
  ```ts
260
- {
261
- steps: [
262
- { type: "warning", message: "content filtered by policy", code: "CONTENT_FILTERED" },
263
- { type: "complete", stopReason: "content_filter" },
264
- ],
265
- }
269
+ yield { type: "warning", message: "content filtered by policy", code: "CONTENT_FILTERED" };
270
+ yield { type: "complete", stopReason: "content_filter" };
266
271
  ```
267
272
 
268
273
  ## 多轮对话
package/dist/index.d.mts CHANGED
@@ -4,13 +4,16 @@
4
4
  *
5
5
  * 覆盖文本、JSON、图片、二进制引用和后端私有内容。
6
6
  */
7
- type ContentBlock = {
7
+ type TextContentBlock = {
8
8
  type: "text";
9
9
  text: string;
10
- } | {
10
+ };
11
+ type JsonContentBlock = {
11
12
  type: "json";
12
13
  json: unknown;
13
- } | {
14
+ };
15
+ type InstructionBlock = TextContentBlock | JsonContentBlock;
16
+ type ContentBlock = InstructionBlock | {
14
17
  type: "image";
15
18
  imageUrl: string;
16
19
  } | {
@@ -25,7 +28,7 @@ type ContentBlock = {
25
28
  type MessageItem = {
26
29
  type: "message";
27
30
  id?: string;
28
- role: "user" | "assistant" | "system" | "developer";
31
+ role: "user" | "assistant";
29
32
  content: ContentBlock[];
30
33
  };
31
34
  type ReasoningItem = {
@@ -78,7 +81,7 @@ type IncludeSettings = {
78
81
  providerMetadata?: "off" | "best_effort";
79
82
  };
80
83
  type AIRequest = {
81
- instructions?: string | ContentBlock[];
84
+ instructions?: string | InstructionBlock[];
82
85
  input: InputItem[];
83
86
  tools?: ToolDefinition[];
84
87
  toolChoice?: ToolChoice;
@@ -542,7 +545,7 @@ type ResponsesAPIRequest = {
542
545
  };
543
546
  type ResponsesInputItem = {
544
547
  type: "message";
545
- role: "user" | "assistant" | "system" | "developer";
548
+ role: "user" | "assistant";
546
549
  content: string;
547
550
  } | {
548
551
  type: "message";
@@ -797,13 +800,18 @@ type MockRequestExpectation = {
797
800
  toolChoice?: "ignore" | "present" | "absent";
798
801
  items?: MockInputExpectation[];
799
802
  };
800
- type MockTurnContext = {
803
+ type MockHistoryRecord = {
804
+ turnIndex: number;
805
+ requestId: string;
806
+ replay: ReplayItem[];
807
+ toolCalls: ToolCallItem[];
808
+ };
809
+ type MockHandlerContext = {
801
810
  turnIndex: number;
802
811
  previousReplay: ReplayItem[];
803
812
  pendingToolCalls: readonly ToolCallItem[];
804
- history: readonly MockTurnRecord[];
813
+ history: readonly MockHistoryRecord[];
805
814
  };
806
- type MockTurnValidator = (request: NormalizedRequest, context: MockTurnContext) => void | Promise<void>;
807
815
  type MockWarningStep = {
808
816
  type: "warning";
809
817
  message: string;
@@ -886,38 +894,25 @@ type MockThrowStep = {
886
894
  error: string | Error;
887
895
  };
888
896
  type MockStep = MockWarningStep | MockAuxiliaryStep | MockMessageStep | MockReasoningStep | MockToolCallStep | MockOutputStep | MockCompleteStep | MockErrorStep | MockInterruptStep | MockThrowStep;
889
- type MockTurn = {
890
- name?: string;
891
- expect?: MockRequestExpectation | MockTurnValidator;
892
- steps: MockStep[];
893
- };
897
+ type MockHandler = (request: NormalizedRequest, context: MockHandlerContext) => AsyncIterable<MockStep>;
898
+ type MockHandlerSource = Iterable<MockStep> | AsyncIterable<MockStep>;
899
+ type MockStaticHandler = (request: NormalizedRequest, context: MockHandlerContext) => MockHandlerSource | Promise<MockHandlerSource>;
894
900
  type MockAdapterOptions = {
895
- turns: MockTurn[];
896
- onExhausted?: "throw" | "repeat-last" | "complete-empty";
901
+ handler: MockHandler;
897
902
  providerMetadata?: Record<string, unknown>;
898
- stream?: MockTextStreamOptions;
899
- };
900
- type MockTurnRecord = {
901
- turnIndex: number;
902
- turnName?: string;
903
- requestId: string;
904
- replay: ReplayItem[];
905
- toolCalls: ToolCallItem[];
906
903
  };
907
904
  type MockProviderRequest = {
908
905
  request: NormalizedRequest;
909
- turn: MockTurn;
906
+ handlerResult: AsyncIterable<MockStep>;
910
907
  turnIndex: number;
911
- turnName?: string;
912
908
  remainingPendingToolCalls: ToolCallItem[];
913
909
  };
910
+ declare function assertMockRequest(request: NormalizedRequest, expectation: MockRequestExpectation, context: MockHandlerContext): void;
914
911
  declare class MockAdapter extends AdapterBase {
915
912
  readonly kind: "mock";
916
913
  readonly nativeStreaming = false;
917
- private readonly turns;
918
- private readonly onExhausted;
914
+ private readonly handler;
919
915
  private readonly providerMetadata?;
920
- private readonly defaultStream?;
921
916
  private cursor;
922
917
  private previousReplay;
923
918
  private pendingToolCalls;
@@ -927,9 +922,9 @@ declare class MockAdapter extends AdapterBase {
927
922
  protected buildRequest(request: NormalizedRequest): Promise<MockProviderRequest>;
928
923
  protected runStream(providerRequest: unknown, factory: EventFactory, request: NormalizedRequest): AsyncIterable<AIStreamEvent>;
929
924
  private finalizeTurn;
930
- private resolveTurn;
931
- private buildTurnContext;
925
+ private buildHandlerContext;
932
926
  }
927
+ declare function withMockStreaming(handler: MockStaticHandler, options: MockTextStreamOptions): MockHandler;
933
928
  //#endregion
934
929
  //#region src/helpers/mapping.d.ts
935
930
  declare function mapStopReason(providerReason: string): StopReason;
@@ -967,9 +962,9 @@ declare function blockToText(b: ContentBlock): string;
967
962
  */
968
963
  declare function contentBlocksToText(blocks: ContentBlock[]): string;
969
964
  /**
970
- * 将 instructions(string | ContentBlock[])归一化为纯文本。
965
+ * 将 instructions(string | InstructionBlock[])归一化为纯文本。
971
966
  */
972
- declare function instructionsToText(instructions: string | ContentBlock[]): string;
967
+ declare function instructionsToText(instructions: string | InstructionBlock[]): string;
973
968
  /**
974
969
  * 从 OutputItem 数组中提取所有 message 类型 item 的文本内容。
975
970
  */
@@ -1051,5 +1046,5 @@ type SyntheticStreamOptions = {
1051
1046
  */
1052
1047
  declare function syntheticStream(options: SyntheticStreamOptions): AsyncIterable<AIStreamEvent>;
1053
1048
  //#endregion
1054
- export { type AIClient, AIError, AIMappingError, AIProviderError, type AIRequest, AIRequestError, type AIResponse, AIStreamError, type AIStreamEvent, AdapterAuxiliaryState, AdapterBase, AuxiliaryCollector, type AuxiliaryFinalizeOptions, type AuxiliaryFinalizeResult, type AuxiliaryInfo, type BackendAdapter, type BackendTrace, type BillingInfo, type BillingPostprocessHook, type BillingSource, ChatCompletionsAdapter, type ChatCompletionsAdapterOptions, type ContentBlock, type CreateAIClientOptions, type EventFactory, type EventFactoryBackend, type EventFactoryState, type FetchFn, type IncludeSettings, type InputItem, type LookupResult, type MessageCompletedEvent, type MessageDeltaEvent, type MessageItem, type MessageStartedEvent, MessagesAdapter, type MessagesAdapterOptions, MockAdapter, type MockAdapterOptions, type MockAuxiliaryStep, type MockCompleteStep, type MockErrorStep, type MockInputExpectation, type MockInterruptStep, type MockMessageStep, type MockOutputStep, type MockReasoningStep, type MockRequestExpectation, type MockStep, type MockTextStreamOptions, type MockThrowStep, type MockToolCallStep, type MockTurn, type MockTurnContext, type MockTurnValidator, type MockWarningStep, type NormalizeOptions, type NormalizedRequest, OllamaAdapter, type OllamaAdapterOptions, type OpaqueItem, type OutputItem, type ReasoningCompletedEvent, type ReasoningDeltaEvent, type ReasoningItem, type ReasoningStartedEvent, type ReplayItem, type ResponseAuxiliaryEvent, type ResponseCompletedEvent, type ResponseStartedEvent, type ResponseWarningEvent, ResponsesAdapter, type ResponsesAdapterOptions, type SSEEvent, type StopReason, type StreamEventBase, type StreamResult, type SyntheticStreamOptions, type ToolCallCompletedEvent, type ToolCallDeltaEvent, type ToolCallItem, type ToolCallStartedEvent, type ToolChoice, type ToolDefinition, type ToolResultItem, type Usage, type UsageSource, type ValidationIssue, WarningCode, aggregateEvents, assertValidRequest, blockToText, collectStream, contentBlocksToText, createAIClient, createEventFactory, emitMalformedStreamWarning, extractText, imageBlock, instructionsToText, jsonBlock, mapReasoningVisibility, mapStopReason, messageItem, metadataSourceList, normalizeRequest, opaqueBlock, opaqueItem, parseSSEEvents, reasoningItem, replayFromOutput, syntheticStream, textBlock, toolCallItem, toolResultItem, validateRequest };
1049
+ export { type AIClient, AIError, AIMappingError, AIProviderError, type AIRequest, AIRequestError, type AIResponse, AIStreamError, type AIStreamEvent, AdapterAuxiliaryState, AdapterBase, AuxiliaryCollector, type AuxiliaryFinalizeOptions, type AuxiliaryFinalizeResult, type AuxiliaryInfo, type BackendAdapter, type BackendTrace, type BillingInfo, type BillingPostprocessHook, type BillingSource, ChatCompletionsAdapter, type ChatCompletionsAdapterOptions, type ContentBlock, type CreateAIClientOptions, type EventFactory, type EventFactoryBackend, type EventFactoryState, type FetchFn, type IncludeSettings, type InputItem, type InstructionBlock, type JsonContentBlock, type LookupResult, type MessageCompletedEvent, type MessageDeltaEvent, type MessageItem, type MessageStartedEvent, MessagesAdapter, type MessagesAdapterOptions, MockAdapter, type MockAdapterOptions, type MockAuxiliaryStep, type MockCompleteStep, type MockErrorStep, type MockHandler, type MockHandlerContext, type MockHistoryRecord, type MockInputExpectation, type MockInterruptStep, type MockMessageStep, type MockOutputStep, type MockReasoningStep, type MockRequestExpectation, type MockStaticHandler, type MockStep, type MockTextStreamOptions, type MockThrowStep, type MockToolCallStep, type MockWarningStep, type NormalizeOptions, type NormalizedRequest, OllamaAdapter, type OllamaAdapterOptions, type OpaqueItem, type OutputItem, type ReasoningCompletedEvent, type ReasoningDeltaEvent, type ReasoningItem, type ReasoningStartedEvent, type ReplayItem, type ResponseAuxiliaryEvent, type ResponseCompletedEvent, type ResponseStartedEvent, type ResponseWarningEvent, ResponsesAdapter, type ResponsesAdapterOptions, type SSEEvent, type StopReason, type StreamEventBase, type StreamResult, type SyntheticStreamOptions, type TextContentBlock, type ToolCallCompletedEvent, type ToolCallDeltaEvent, type ToolCallItem, type ToolCallStartedEvent, type ToolChoice, type ToolDefinition, type ToolResultItem, type Usage, type UsageSource, type ValidationIssue, WarningCode, aggregateEvents, assertMockRequest, assertValidRequest, blockToText, collectStream, contentBlocksToText, createAIClient, createEventFactory, emitMalformedStreamWarning, extractText, imageBlock, instructionsToText, jsonBlock, mapReasoningVisibility, mapStopReason, messageItem, metadataSourceList, normalizeRequest, opaqueBlock, opaqueItem, parseSSEEvents, reasoningItem, replayFromOutput, syntheticStream, textBlock, toolCallItem, toolResultItem, validateRequest, withMockStreaming };
1055
1050
  //# sourceMappingURL=index.d.mts.map