@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.
- package/README.md +34 -38
- package/dist/index.d.mts +18 -40
- package/dist/index.mjs +88 -139
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
- package/src/adapters/chat-completions.ts +4 -22
- package/src/adapters/messages.ts +10 -28
- package/src/adapters/mock.ts +9 -12
- package/src/adapters/ollama.ts +26 -53
- package/src/adapters/responses.ts +17 -35
- package/src/core/client.ts +15 -2
- package/src/core/validation.ts +19 -0
- package/src/helpers/adapter-base.ts +6 -3
- package/src/helpers/index.ts +0 -1
- package/src/helpers/mapping.ts +1 -2
- package/src/helpers/request-mapper.ts +18 -25
- package/src/index.ts +1 -1
- package/src/types/adapter.ts +3 -12
- package/src/types/index.ts +1 -9
- package/src/types/items.ts +0 -1
- package/src/types/request.ts +2 -0
package/README.md
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
#
|
|
1
|
+
# @codehz/ai
|
|
2
2
|
|
|
3
|
-
统一流式 AI 客户端,提供一套 canonical API,对接真实模型后端与面向测试的回调驱动 `MockAdapter`(`responses` / `messages` / `chat
|
|
3
|
+
统一流式 AI 客户端,提供一套 canonical API,对接真实模型后端与面向测试的回调驱动 `MockAdapter`(`responses` / `messages` / `chat-completions` / `ollama` / `mock`)。
|
|
4
4
|
|
|
5
5
|
## 安装
|
|
6
6
|
|
|
7
7
|
```bash
|
|
8
|
-
bun add
|
|
8
|
+
bun add @codehz/ai
|
|
9
9
|
```
|
|
10
10
|
|
|
11
11
|
依赖:Bun(内置 `fetch`、`crypto`),无需额外运行时依赖。
|
|
@@ -13,7 +13,7 @@ bun add nano-ai
|
|
|
13
13
|
## 快速开始
|
|
14
14
|
|
|
15
15
|
```ts
|
|
16
|
-
import { createAIClient, ResponsesAdapter } from "
|
|
16
|
+
import { createAIClient, ResponsesAdapter } from "@codehz/ai";
|
|
17
17
|
|
|
18
18
|
const client = createAIClient({
|
|
19
19
|
adapter: new ResponsesAdapter({ apiKey: process.env.OPENAI_API_KEY! }),
|
|
@@ -69,22 +69,22 @@ response.started → (item.started → item.delta* → item.completed)* → resp
|
|
|
69
69
|
|
|
70
70
|
事件类型:
|
|
71
71
|
|
|
72
|
-
| 事件 | 含义
|
|
73
|
-
| ------------------------------------- |
|
|
74
|
-
| `response.started` | 响应开始
|
|
75
|
-
| `message.{started,delta,completed}` | 消息输出
|
|
76
|
-
| `reasoning.{started,delta,completed}` | 思维链
|
|
77
|
-
| `tool_call.{started,delta,completed}` | 工具调用
|
|
78
|
-
| `response.warning` | 非致命警告
|
|
79
|
-
| `response.auxiliary` | usage / billing 辅助信息
|
|
80
|
-
| `response.completed` |
|
|
72
|
+
| 事件 | 含义 |
|
|
73
|
+
| ------------------------------------- | ------------------------------------------- |
|
|
74
|
+
| `response.started` | 响应开始 |
|
|
75
|
+
| `message.{started,delta,completed}` | 消息输出 |
|
|
76
|
+
| `reasoning.{started,delta,completed}` | 思维链 |
|
|
77
|
+
| `tool_call.{started,delta,completed}` | 工具调用 |
|
|
78
|
+
| `response.warning` | 非致命警告 |
|
|
79
|
+
| `response.auxiliary` | usage / billing 辅助信息 |
|
|
80
|
+
| `response.completed` | 响应结束,携带 replay、终止原因及最终元数据 |
|
|
81
81
|
|
|
82
82
|
### 统一终结结果
|
|
83
83
|
|
|
84
84
|
流结束后可通过 `collectStream()` 聚合为 `AIResponse`:
|
|
85
85
|
|
|
86
86
|
```ts
|
|
87
|
-
import { collectStream } from "
|
|
87
|
+
import { collectStream } from "@codehz/ai";
|
|
88
88
|
|
|
89
89
|
const response = await collectStream(client.stream({ input }));
|
|
90
90
|
console.log(response.text); // 全部文本
|
|
@@ -95,17 +95,18 @@ console.log(response.replay); // 续接材料
|
|
|
95
95
|
|
|
96
96
|
`AIResponse` 包含:
|
|
97
97
|
|
|
98
|
-
| 字段 | 类型 | 说明
|
|
99
|
-
| ------------ | ---------------- |
|
|
100
|
-
| `output` | `OutputItem[]` | 当前轮输出
|
|
101
|
-
| `replay` | `ReplayItem[]` | 续接材料(下次请求带回)
|
|
102
|
-
| `text` | `string` | 全部文本拼接
|
|
103
|
-
| `toolCalls` | `ToolCallItem[]` | 工具调用
|
|
104
|
-
| `stopReason` | `StopReason
|
|
105
|
-
| `usage` | `Usage
|
|
106
|
-
| `billing` | `BillingInfo
|
|
107
|
-
| `
|
|
108
|
-
| `
|
|
98
|
+
| 字段 | 类型 | 说明 |
|
|
99
|
+
| ------------ | ---------------- | ------------------------- |
|
|
100
|
+
| `output` | `OutputItem[]` | 当前轮输出 |
|
|
101
|
+
| `replay` | `ReplayItem[]` | 续接材料(下次请求带回) |
|
|
102
|
+
| `text` | `string` | 全部文本拼接 |
|
|
103
|
+
| `toolCalls` | `ToolCallItem[]` | 工具调用 |
|
|
104
|
+
| `stopReason` | `StopReason?` | 终止原因(可选) |
|
|
105
|
+
| `usage` | `Usage?` | token 统计(可选) |
|
|
106
|
+
| `billing` | `BillingInfo?` | 计费信息(可选) |
|
|
107
|
+
| `auxiliary` | `AuxiliaryInfo?` | Provider 辅助信息(可选) |
|
|
108
|
+
| `warnings` | `string[]?` | 非致命警告(可选) |
|
|
109
|
+
| `backend` | `BackendTrace` | 调用链路元数据 |
|
|
109
110
|
|
|
110
111
|
流式 `message.delta` / `reasoning.delta` 保持后端分片粒度;完成态 `output` 中的
|
|
111
112
|
`message` / `reasoning` 会合并相邻 `text` content blocks(直接拼接且不添加分隔符),
|
|
@@ -129,7 +130,7 @@ import {
|
|
|
129
130
|
OllamaAdapter,
|
|
130
131
|
MockAdapter,
|
|
131
132
|
withMockStreaming,
|
|
132
|
-
} from "
|
|
133
|
+
} from "@codehz/ai";
|
|
133
134
|
|
|
134
135
|
// OpenAI Responses API
|
|
135
136
|
const responses = new ResponsesAdapter({ apiKey: "sk-..." });
|
|
@@ -163,20 +164,15 @@ const mock = new MockAdapter({
|
|
|
163
164
|
});
|
|
164
165
|
```
|
|
165
166
|
|
|
166
|
-
公开 adapter
|
|
167
|
+
公开 adapter 接口暴露稳定标识和流来源:
|
|
167
168
|
|
|
168
169
|
```ts
|
|
169
170
|
adapter.kind; // "responses" | "messages" | "chat-completions" | ...
|
|
170
|
-
adapter.
|
|
171
|
-
adapter.capabilities.reasoningStreaming;
|
|
172
|
-
adapter.capabilities.toolCallStreaming;
|
|
173
|
-
adapter.capabilities.replay; // "canonical" | "opaque" | "none"
|
|
174
|
-
adapter.capabilities.usage; // "stream" | "final" | "none"
|
|
175
|
-
adapter.capabilities.toolResultOutcomes;
|
|
171
|
+
adapter.isSyntheticStream;
|
|
176
172
|
```
|
|
177
173
|
|
|
178
|
-
|
|
179
|
-
|
|
174
|
+
响应级 `backend.isSyntheticStream` 使用同一标记;具体响应内容仍应从
|
|
175
|
+
本次事件流、warning 和 `replay` 判断。
|
|
180
176
|
|
|
181
177
|
## Mock 后端
|
|
182
178
|
|
|
@@ -208,7 +204,7 @@ const handler = withMockStreaming(
|
|
|
208
204
|
- 可用 `assertMockRequest()` 验证调用方是否把上一轮 `replay` 和当前 `tool_result` 正确带回
|
|
209
205
|
|
|
210
206
|
```ts
|
|
211
|
-
import { assertMockRequest, createAIClient, MockAdapter } from "
|
|
207
|
+
import { assertMockRequest, createAIClient, MockAdapter } from "@codehz/ai";
|
|
212
208
|
|
|
213
209
|
const client = createAIClient({
|
|
214
210
|
adapter: new MockAdapter({
|
|
@@ -325,7 +321,7 @@ const r2 = await collectStream(client.stream({ input, tools }));
|
|
|
325
321
|
非流式后端可通过 `syntheticStream()` 包装为规范事件流:
|
|
326
322
|
|
|
327
323
|
```ts
|
|
328
|
-
import { syntheticStream } from "
|
|
324
|
+
import { syntheticStream } from "@codehz/ai";
|
|
329
325
|
|
|
330
326
|
const events = syntheticStream({
|
|
331
327
|
model: "gpt-4o",
|
|
@@ -345,7 +341,7 @@ for await (const event of events) {
|
|
|
345
341
|
`AuxiliaryCollector` 提供分层 best-effort 采集(流事件 → headers → lookup → derived):
|
|
346
342
|
|
|
347
343
|
```ts
|
|
348
|
-
import { AuxiliaryCollector } from "
|
|
344
|
+
import { AuxiliaryCollector } from "@codehz/ai";
|
|
349
345
|
|
|
350
346
|
const collector = new AuxiliaryCollector();
|
|
351
347
|
collector.recordUsage({ inputTokens: 10, outputTokens: 5 }, "stream");
|
package/dist/index.d.mts
CHANGED
|
@@ -42,7 +42,6 @@ type ToolCallItem = {
|
|
|
42
42
|
id: string;
|
|
43
43
|
name: string;
|
|
44
44
|
argumentsText: string;
|
|
45
|
-
argumentsJson?: unknown;
|
|
46
45
|
};
|
|
47
46
|
type ToolResultItem = {
|
|
48
47
|
type: "tool_result";
|
|
@@ -88,7 +87,8 @@ type AIRequest = {
|
|
|
88
87
|
include?: IncludeSettings;
|
|
89
88
|
metadata?: Record<string, string>;
|
|
90
89
|
temperature?: number;
|
|
91
|
-
maxOutputTokens?: number;
|
|
90
|
+
maxOutputTokens?: number; /** AbortSignal 用于打断请求。abort 时 fetch 调用会被取消,流迭代器抛出 AbortError。 */
|
|
91
|
+
signal?: AbortSignal;
|
|
92
92
|
};
|
|
93
93
|
//#endregion
|
|
94
94
|
//#region src/types/response.d.ts
|
|
@@ -233,24 +233,16 @@ type NormalizedRequest = AIRequest & {
|
|
|
233
233
|
model: string;
|
|
234
234
|
requestId: string;
|
|
235
235
|
};
|
|
236
|
-
type StreamingCapability = "native" | "synthetic" | "none";
|
|
237
|
-
type AdapterCapabilities = {
|
|
238
|
-
readonly textStreaming: StreamingCapability;
|
|
239
|
-
readonly reasoningStreaming: StreamingCapability;
|
|
240
|
-
readonly toolCallStreaming: StreamingCapability;
|
|
241
|
-
readonly replay: "canonical" | "opaque" | "none";
|
|
242
|
-
readonly usage: "stream" | "final" | "none";
|
|
243
|
-
readonly toolResultOutcomes: ReadonlyArray<"success" | "error" | "rejected">;
|
|
244
|
-
};
|
|
245
236
|
interface BackendAdapter {
|
|
246
237
|
readonly kind: "chat-completions" | "messages" | "responses" | "ollama" | "mock";
|
|
247
|
-
readonly
|
|
238
|
+
readonly isSyntheticStream: boolean;
|
|
248
239
|
stream(request: NormalizedRequest): AsyncIterable<AIStreamEvent>;
|
|
249
240
|
}
|
|
250
241
|
type CreateAIClientOptions = {
|
|
251
242
|
adapter: BackendAdapter;
|
|
252
243
|
model: string;
|
|
253
|
-
defaults?: Partial<AIRequest>;
|
|
244
|
+
defaults?: Partial<AIRequest>; /** 全局默认 AbortSignal,当 request.signal 未设置时生效。 */
|
|
245
|
+
signal?: AbortSignal;
|
|
254
246
|
};
|
|
255
247
|
interface AIClient {
|
|
256
248
|
stream(request: AIRequest): AsyncIterable<AIStreamEvent>;
|
|
@@ -523,7 +515,7 @@ type StreamResult = {
|
|
|
523
515
|
};
|
|
524
516
|
declare abstract class AdapterBase implements BackendAdapter {
|
|
525
517
|
abstract readonly kind: "chat-completions" | "messages" | "responses" | "ollama" | "mock";
|
|
526
|
-
abstract readonly
|
|
518
|
+
abstract readonly isSyntheticStream: boolean;
|
|
527
519
|
/**
|
|
528
520
|
* stream 模板方法:
|
|
529
521
|
* 1. 创建事件工厂,发射 response.started
|
|
@@ -616,7 +608,7 @@ type ResponsesTool = {
|
|
|
616
608
|
};
|
|
617
609
|
declare class ResponsesAdapter extends AdapterBase {
|
|
618
610
|
readonly kind: "responses";
|
|
619
|
-
readonly
|
|
611
|
+
readonly isSyntheticStream = false;
|
|
620
612
|
private apiKey;
|
|
621
613
|
private baseUrl;
|
|
622
614
|
private fetchFn;
|
|
@@ -684,7 +676,7 @@ type MessagesAPITool = {
|
|
|
684
676
|
};
|
|
685
677
|
declare class MessagesAdapter extends AdapterBase {
|
|
686
678
|
readonly kind: "messages";
|
|
687
|
-
readonly
|
|
679
|
+
readonly isSyntheticStream = false;
|
|
688
680
|
private apiKey;
|
|
689
681
|
private apiVersion;
|
|
690
682
|
private baseUrl;
|
|
@@ -742,7 +734,7 @@ type ChatTool = {
|
|
|
742
734
|
};
|
|
743
735
|
declare class ChatCompletionsAdapter extends AdapterBase {
|
|
744
736
|
readonly kind: "chat-completions";
|
|
745
|
-
readonly
|
|
737
|
+
readonly isSyntheticStream = false;
|
|
746
738
|
private apiKey;
|
|
747
739
|
private baseUrl;
|
|
748
740
|
private fetchFn;
|
|
@@ -790,7 +782,7 @@ type OllamaTool = {
|
|
|
790
782
|
};
|
|
791
783
|
declare class OllamaAdapter extends AdapterBase {
|
|
792
784
|
readonly kind: "ollama";
|
|
793
|
-
readonly
|
|
785
|
+
readonly isSyntheticStream = false;
|
|
794
786
|
private baseUrl;
|
|
795
787
|
private apiKey;
|
|
796
788
|
private fetchFn;
|
|
@@ -839,7 +831,8 @@ type MockHandlerContext = {
|
|
|
839
831
|
turnIndex: number;
|
|
840
832
|
previousReplay: ReplayItem[];
|
|
841
833
|
pendingToolCalls: readonly ToolCallItem[];
|
|
842
|
-
history: readonly MockHistoryRecord[];
|
|
834
|
+
history: readonly MockHistoryRecord[]; /** 请求的 AbortSignal,handler 可检查 signal.aborted 提前退出。 */
|
|
835
|
+
signal?: AbortSignal;
|
|
843
836
|
};
|
|
844
837
|
type MockWarningStep = {
|
|
845
838
|
type: "warning";
|
|
@@ -886,7 +879,6 @@ type MockToolCallStep = {
|
|
|
886
879
|
id: string;
|
|
887
880
|
name: string;
|
|
888
881
|
argumentsText: string;
|
|
889
|
-
argumentsJson?: unknown;
|
|
890
882
|
streamArguments?: boolean;
|
|
891
883
|
stream?: MockTextStreamOptions | false;
|
|
892
884
|
};
|
|
@@ -939,14 +931,7 @@ type MockProviderRequest = {
|
|
|
939
931
|
declare function assertMockRequest(request: NormalizedRequest, expectation: MockRequestExpectation, context: MockHandlerContext): void;
|
|
940
932
|
declare class MockAdapter extends AdapterBase {
|
|
941
933
|
readonly kind: "mock";
|
|
942
|
-
readonly
|
|
943
|
-
readonly textStreaming: "synthetic";
|
|
944
|
-
readonly reasoningStreaming: "synthetic";
|
|
945
|
-
readonly toolCallStreaming: "synthetic";
|
|
946
|
-
readonly replay: "canonical";
|
|
947
|
-
readonly usage: "final";
|
|
948
|
-
readonly toolResultOutcomes: readonly ["success", "error", "rejected"];
|
|
949
|
-
};
|
|
934
|
+
readonly isSyntheticStream = true;
|
|
950
935
|
private readonly handler;
|
|
951
936
|
private readonly providerMetadata?;
|
|
952
937
|
private cursor;
|
|
@@ -979,7 +964,7 @@ declare function opaqueBlock(payload: unknown): ContentBlock & {
|
|
|
979
964
|
};
|
|
980
965
|
declare function messageItem(content: ContentBlock[], overrides?: Partial<Omit<MessageItem, "type" | "content">>): MessageItem;
|
|
981
966
|
declare function reasoningItem(content: ContentBlock[], visibility?: ReasoningItem["visibility"], id?: string): ReasoningItem;
|
|
982
|
-
declare function toolCallItem(id: string, name: string, argumentsText: string
|
|
967
|
+
declare function toolCallItem(id: string, name: string, argumentsText: string): ToolCallItem;
|
|
983
968
|
declare function toolResultItem(callId: string, toolName: string, outcome: ToolResultItem["outcome"], content: ContentBlock[]): ToolResultItem;
|
|
984
969
|
declare function opaqueItem(source: OpaqueItem["source"], purpose: OpaqueItem["purpose"], payload: unknown, id?: string): OpaqueItem;
|
|
985
970
|
/**
|
|
@@ -1190,27 +1175,20 @@ declare function splitLines(buffer: string, allowEOF: boolean): StreamSplitResul
|
|
|
1190
1175
|
declare function splitSSEFrames(buffer: string, allowEOF: boolean): StreamSplitResult;
|
|
1191
1176
|
//#endregion
|
|
1192
1177
|
//#region src/helpers/request-mapper.d.ts
|
|
1193
|
-
type ProviderProfile = {
|
|
1194
|
-
readonly kind: string;
|
|
1195
|
-
readonly instructionsMode: "system_message" | "instructions_field" | "none";
|
|
1196
|
-
readonly supportedBlockTypes: ReadonlyArray<ContentBlock["type"]>;
|
|
1197
|
-
readonly reasoningBlockTypes: ReadonlyArray<ContentBlock["type"]>;
|
|
1198
|
-
readonly capabilities: AdapterCapabilities;
|
|
1199
|
-
};
|
|
1200
1178
|
declare class NormalizedRequestMapper {
|
|
1201
|
-
readonly
|
|
1202
|
-
constructor(
|
|
1179
|
+
readonly kind: string;
|
|
1180
|
+
constructor(kind: string);
|
|
1203
1181
|
mapInstructions(instructions: string | InstructionBlock[]): string;
|
|
1204
1182
|
ensureTextBlocks(blocks: ContentBlock[], field: string): ContentBlock[];
|
|
1205
1183
|
ensureReasoningBlocks(blocks: ContentBlock[], field: string): Array<Extract<ContentBlock, {
|
|
1206
1184
|
type: "text";
|
|
1207
1185
|
}>>;
|
|
1208
|
-
|
|
1186
|
+
parseToolArguments(item: ToolCallItem): Record<string, unknown>;
|
|
1209
1187
|
rollbackTrailingAssistantMessages<T extends {
|
|
1210
1188
|
role: string;
|
|
1211
1189
|
}>(messages: T[]): void;
|
|
1212
1190
|
private ensureBlocks;
|
|
1213
1191
|
}
|
|
1214
1192
|
//#endregion
|
|
1215
|
-
export { type AIClient, AIError, AIMappingError, AIProviderError, type AIRequest, AIRequestError, type AIResponse, AIStreamError, type AIStreamEvent, AdapterAuxiliaryState, AdapterBase,
|
|
1193
|
+
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, IncrementalStreamParser, type InputItem, type InstructionBlock, type JsonContentBlock, type LookupResult, MAX_OPAQUE_JSON_DEPTH, MAX_OPAQUE_PAYLOAD_BYTES, 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, NormalizedRequestMapper, OllamaAdapter, type OllamaAdapterOptions, type OpaqueEnvelopeResult, type OpaqueItem, type OutputItem, PROVIDER_ERROR_MESSAGE_MAX_LEN, PROVIDER_ERROR_RAW_BODY_THRESHOLD, 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 StreamParseResult, type StreamResult, type StreamSplitResult, 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, assertOpaqueReplayEnvelope, assertValidRequest, blockToText, collectStream, contentBlocksToText, createAIClient, createEventFactory, emitMalformedStreamWarning, extractProviderErrorMessage, extractText, imageBlock, instructionsToText, jsonBlock, mapReasoningVisibility, mapStopReason, measureJsonDepth, messageItem, metadataSourceList, normalizeRequest, opaqueBlock, opaqueItem, parseSSEEvents, providerHttpError, reasoningItem, replayFromOutput, splitLines, splitSSEFrames, syntheticStream, textBlock, toolCallItem, toolResultItem, usageFromAnthropicMessages, usageFromChatCompletions, usageFromOllama, usageFromOpenAIResponses, validateOpaqueReplayEnvelope, validateRequest, withMockStreaming };
|
|
1216
1194
|
//# sourceMappingURL=index.d.mts.map
|