@codehz/ai 0.4.4 → 0.4.6

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.
@@ -0,0 +1,56 @@
1
+ name: Publish Package
2
+
3
+ on:
4
+ push:
5
+ tags:
6
+ - "v*"
7
+
8
+ permissions:
9
+ contents: read
10
+ id-token: write # required for npm trusted publishing (OIDC)
11
+
12
+ jobs:
13
+ publish:
14
+ runs-on: ubuntu-latest
15
+ steps:
16
+ - uses: actions/checkout@v6
17
+
18
+ - name: Verify tag matches package.json version
19
+ run: |
20
+ tag="${GITHUB_REF_NAME#v}"
21
+ version="$(node -p "require('./package.json').version")"
22
+ if [ "$tag" != "$version" ]; then
23
+ echo "Tag v${tag} does not match package.json version ${version}"
24
+ exit 1
25
+ fi
26
+
27
+ - uses: oven-sh/setup-bun@v2
28
+ with:
29
+ bun-version: latest
30
+
31
+ - name: Install dependencies
32
+ run: bun install --frozen-lockfile
33
+
34
+ - name: Typecheck
35
+ run: bun run typecheck
36
+
37
+ - name: Lint
38
+ run: bun run lint
39
+
40
+ - name: Test
41
+ run: bun test
42
+
43
+ # Trusted publishing requires npm CLI (OIDC); other package managers cannot publish this way.
44
+ - uses: actions/setup-node@v6
45
+ with:
46
+ node-version: "24"
47
+ registry-url: "https://registry.npmjs.org"
48
+ package-manager-cache: false # never cache package manager state for release builds
49
+
50
+ - name: Ensure npm CLI supports trusted publishing
51
+ run: npm install -g npm@latest
52
+
53
+ - name: Publish to npm
54
+ # No NPM_TOKEN: npm CLI exchanges the GitHub OIDC token automatically.
55
+ # Provenance is generated automatically for trusted publishing from public repos.
56
+ run: npm publish --access public
package/README.md CHANGED
@@ -45,10 +45,21 @@ type AIRequest = {
45
45
  toolChoice?: ToolChoice; // 工具选择策略
46
46
  temperature?: number; // 温度 (0–2)
47
47
  maxOutputTokens?: number; // 最大输出 token
48
+ reasoningLevel?: "none" | "minimal" | "low" | "medium" | "high" | "xhigh" | "max"; // 可移植思考力度
48
49
  include?: { usage?; billing?; providerMetadata? };
49
50
  };
50
51
  ```
51
52
 
53
+ `reasoningLevel` 是 portable 枚举,由各 adapter 映射到 provider 原生字段;未设置时不写相关 wire 字段。adapter 无法映射的 level(如 Ollama 的 `minimal` / `xhigh` / `max`)会抛 `AIRequestError`(`UNSUPPORTED_REASONING_LEVEL`)。需要 budget / summary 等特化参数时,仍可用构造期 `extraBody` 覆盖同名顶层键。
54
+
55
+ | Adapter | 映射 |
56
+ | --- | --- |
57
+ | `ResponsesAdapter` | `reasoning: { effort }` |
58
+ | `ChatCompletionsAdapter` | 顶层 `reasoning_effort` |
59
+ | `MessagesAdapter` | `thinking: { type: "disabled" }` 或 `{ type: "enabled", budget_tokens }`(由 `maxOutputTokens` 按比例推导,默认 4096) |
60
+ | `OllamaAdapter` | `think: false \| "low" \| "medium" \| "high"` |
61
+ | `MockAdapter` | 透传到 `MockHandlerContext.reasoningLevel` |
62
+
52
63
  `input` 是 item 数组,每个 item 可以是:
53
64
 
54
65
  | Item 类型 | 用途 |
@@ -133,16 +144,33 @@ import {
133
144
  } from "@codehz/ai";
134
145
 
135
146
  // OpenAI Responses API
136
- const responses = new ResponsesAdapter({ apiKey: "sk-..." });
147
+ const responses = new ResponsesAdapter({
148
+ apiKey: "sk-...",
149
+ // 可选:自定义请求头 / body 额外顶层字段(构造期静态,后写覆盖内置鉴权头与同名 body 键)
150
+ headers: { "OpenAI-Organization": "org-..." },
151
+ extraBody: { top_p: 0.9 },
152
+ });
137
153
 
138
154
  // Anthropic Messages API
139
- const messages = new MessagesAdapter({ apiKey: "sk-ant-..." });
155
+ const messages = new MessagesAdapter({
156
+ apiKey: "sk-ant-...",
157
+ headers: { "anthropic-beta": "..." },
158
+ extraBody: { top_p: 0.9 },
159
+ });
140
160
 
141
161
  // OpenAI Chat Completions
142
- const chat = new ChatCompletionsAdapter({ apiKey: "sk-..." });
162
+ const chat = new ChatCompletionsAdapter({
163
+ apiKey: "sk-...",
164
+ headers: { "OpenAI-Organization": "org-..." },
165
+ extraBody: { top_p: 0.9 },
166
+ });
143
167
 
144
168
  // Ollama
145
- const ollama = new OllamaAdapter({ baseUrl: "http://localhost:11434" });
169
+ const ollama = new OllamaAdapter({
170
+ baseUrl: "http://localhost:11434",
171
+ headers: { "X-Custom": "..." },
172
+ extraBody: { keep_alive: "10m" },
173
+ });
146
174
 
147
175
  // 面向测试的回调驱动 mock backend
148
176
  const mock = new MockAdapter({
package/dist/index.d.mts CHANGED
@@ -79,6 +79,8 @@ type IncludeSettings = {
79
79
  billing?: "off" | "best_effort";
80
80
  providerMetadata?: "off" | "best_effort";
81
81
  };
82
+ /** Portable reasoning / thinking effort. Mapped per-adapter to provider wire fields. */
83
+ type ReasoningLevel = "none" | "minimal" | "low" | "medium" | "high" | "xhigh" | "max";
82
84
  type AIRequest = {
83
85
  instructions?: string | InstructionBlock[];
84
86
  input: InputItem[];
@@ -87,7 +89,13 @@ type AIRequest = {
87
89
  include?: IncludeSettings;
88
90
  metadata?: Record<string, string>;
89
91
  temperature?: number;
90
- maxOutputTokens?: number; /** AbortSignal 用于打断请求。abort 时 fetch 调用会被取消,流迭代器抛出 AbortError。 */
92
+ maxOutputTokens?: number;
93
+ /**
94
+ * Portable reasoning effort. Adapters map this to provider-native fields
95
+ * (e.g. Responses `reasoning.effort`, Chat Completions `reasoning_effort`,
96
+ * Messages `thinking`, Ollama `think`). Unsupported levels throw.
97
+ */
98
+ reasoningLevel?: ReasoningLevel; /** AbortSignal 用于打断请求。abort 时 fetch 调用会被取消,流迭代器抛出 AbortError。 */
91
99
  signal?: AbortSignal;
92
100
  };
93
101
  //#endregion
@@ -551,7 +559,9 @@ declare abstract class AdapterBase implements BackendAdapter {
551
559
  type ResponsesAdapterOptions = {
552
560
  apiKey: string;
553
561
  baseUrl?: string; /** 可注入自定义 fetch 实现(用于测试/代理) */
554
- fetch?: FetchFn;
562
+ fetch?: FetchFn; /** 额外请求头;后写覆盖内置 Authorization / Content-Type */
563
+ headers?: Record<string, string>; /** 额外 body 顶层字段;浅层合并,同名键可覆盖 */
564
+ extraBody?: Record<string, unknown>;
555
565
  };
556
566
  type ResponsesAPIRequest = {
557
567
  model: string;
@@ -564,7 +574,10 @@ type ResponsesAPIRequest = {
564
574
  };
565
575
  metadata?: Record<string, string>;
566
576
  temperature?: number;
567
- max_output_tokens?: number; /** 服务端多轮续写;opaque replay response id 映射到此字段,而非 item_reference */
577
+ max_output_tokens?: number; /** Portable reasoningLevel effort;summary 等特化字段不在此层 */
578
+ reasoning?: {
579
+ effort: string;
580
+ }; /** 服务端多轮续写;opaque replay 的 response id 映射到此字段,而非 item_reference */
568
581
  previous_response_id?: string;
569
582
  stream: true;
570
583
  };
@@ -637,6 +650,8 @@ declare class ResponsesAdapter extends AdapterBase {
637
650
  private apiKey;
638
651
  private baseUrl;
639
652
  private fetchFn;
653
+ private headers;
654
+ private extraBody;
640
655
  constructor(options: ResponsesAdapterOptions);
641
656
  protected buildRequest(request: NormalizedRequest): ResponsesAPIRequest;
642
657
  protected runStream(providerRequest: ResponsesAPIRequest, factory: EventFactory, request: NormalizedRequest): AsyncIterable<AIStreamEvent>;
@@ -648,7 +663,9 @@ type MessagesAdapterOptions = {
648
663
  apiKey: string;
649
664
  apiVersion?: string;
650
665
  baseUrl?: string; /** 可注入自定义 fetch 实现(用于测试/代理) */
651
- fetch?: FetchFn;
666
+ fetch?: FetchFn; /** 额外请求头;后写覆盖内置 x-api-key / Content-Type / anthropic-version */
667
+ headers?: Record<string, string>; /** 额外 body 顶层字段;浅层合并,同名键可覆盖 */
668
+ extraBody?: Record<string, unknown>;
652
669
  };
653
670
  type MessagesAPIRequest = {
654
671
  model: string;
@@ -666,6 +683,8 @@ type MessagesAPIRequest = {
666
683
  thinking?: {
667
684
  type: "enabled";
668
685
  budget_tokens: number;
686
+ } | {
687
+ type: "disabled";
669
688
  };
670
689
  stream: true;
671
690
  };
@@ -706,6 +725,8 @@ declare class MessagesAdapter extends AdapterBase {
706
725
  private apiVersion;
707
726
  private baseUrl;
708
727
  private fetchFn;
728
+ private headers;
729
+ private extraBody;
709
730
  constructor(options: MessagesAdapterOptions);
710
731
  protected buildRequest(request: NormalizedRequest): MessagesAPIRequest;
711
732
  protected runStream(providerRequest: MessagesAPIRequest, factory: EventFactory, request: NormalizedRequest): AsyncIterable<AIStreamEvent>;
@@ -715,7 +736,9 @@ declare class MessagesAdapter extends AdapterBase {
715
736
  type ChatCompletionsAdapterOptions = {
716
737
  apiKey: string;
717
738
  baseUrl?: string;
718
- fetch?: FetchFn;
739
+ fetch?: FetchFn; /** 额外请求头;后写覆盖内置 Authorization / Content-Type */
740
+ headers?: Record<string, string>; /** 额外 body 顶层字段;浅层合并,同名键可覆盖 */
741
+ extraBody?: Record<string, unknown>;
719
742
  };
720
743
  type ChatRequest = {
721
744
  model: string;
@@ -729,7 +752,8 @@ type ChatRequest = {
729
752
  };
730
753
  metadata?: Record<string, string>;
731
754
  temperature?: number;
732
- max_tokens?: number;
755
+ max_tokens?: number; /** Portable reasoningLevel → reasoning_effort */
756
+ reasoning_effort?: string;
733
757
  stream: true;
734
758
  n: 1;
735
759
  };
@@ -763,6 +787,8 @@ declare class ChatCompletionsAdapter extends AdapterBase {
763
787
  private apiKey;
764
788
  private baseUrl;
765
789
  private fetchFn;
790
+ private headers;
791
+ private extraBody;
766
792
  constructor(options: ChatCompletionsAdapterOptions);
767
793
  protected buildRequest(request: NormalizedRequest): ChatRequest;
768
794
  protected runStream(providerRequest: ChatRequest, factory: EventFactory, request: NormalizedRequest): AsyncIterable<AIStreamEvent>;
@@ -772,13 +798,16 @@ declare class ChatCompletionsAdapter extends AdapterBase {
772
798
  type OllamaAdapterOptions = {
773
799
  /** Ollama 服务地址,默认 http://localhost:11434 */baseUrl?: string; /** 可选 API key(用于需要认证的代理场景) */
774
800
  apiKey?: string; /** 可注入自定义 fetch 实现 */
775
- fetch?: FetchFn;
801
+ fetch?: FetchFn; /** 额外请求头;后写覆盖内置 Content-Type / Authorization */
802
+ headers?: Record<string, string>; /** 额外 body 顶层字段;浅层合并,同名键可覆盖 */
803
+ extraBody?: Record<string, unknown>;
776
804
  };
777
805
  type OllamaChatRequest = {
778
806
  model: string;
779
807
  messages: OllamaMessage[];
780
808
  stream: true;
781
- tools?: OllamaTool[];
809
+ tools?: OllamaTool[]; /** Portable reasoningLevel → think;minimal/xhigh/max 不支持 */
810
+ think?: boolean | "low" | "medium" | "high";
782
811
  options?: {
783
812
  temperature?: number;
784
813
  num_predict?: number;
@@ -811,6 +840,8 @@ declare class OllamaAdapter extends AdapterBase {
811
840
  private baseUrl;
812
841
  private apiKey;
813
842
  private fetchFn;
843
+ private headers;
844
+ private extraBody;
814
845
  constructor(options?: OllamaAdapterOptions);
815
846
  protected buildRequest(request: NormalizedRequest): OllamaChatRequest;
816
847
  protected runStream(providerRequest: OllamaChatRequest, factory: EventFactory, request: NormalizedRequest): AsyncIterable<AIStreamEvent>;
@@ -857,7 +888,8 @@ type MockHandlerContext = {
857
888
  previousReplay: ReplayItem[];
858
889
  pendingToolCalls: readonly ToolCallItem[];
859
890
  history: readonly MockHistoryRecord[]; /** 请求的 AbortSignal,handler 可检查 signal.aborted 提前退出。 */
860
- signal?: AbortSignal;
891
+ signal?: AbortSignal; /** 当前请求的 portable reasoningLevel(若设置)。 */
892
+ reasoningLevel?: ReasoningLevel;
861
893
  };
862
894
  type MockWarningStep = {
863
895
  type: "warning";
@@ -1200,6 +1232,52 @@ declare function createCompletionGate(): {
1200
1232
  tryComplete(): boolean;
1201
1233
  };
1202
1234
  //#endregion
1235
+ //#region src/helpers/provider-request-options.d.ts
1236
+ /**
1237
+ * Provider 请求 headers / body 扩展合并
1238
+ *
1239
+ * 供真实后端 adapter 构造选项 `headers` / `extraBody` 使用:
1240
+ * - headers:内置鉴权头为基,自定义后写覆盖
1241
+ * - extraBody:已构建 body 为基,额外字段浅层 spread,同名顶层键可覆盖
1242
+ */
1243
+ /** 合并内置 headers 与自定义 headers;自定义后写覆盖同名键。 */
1244
+ declare function mergeProviderHeaders(base: Record<string, string>, custom?: Record<string, string>): Record<string, string>;
1245
+ /**
1246
+ * 将构造期 extraBody 浅层合并到已构建的 provider body。
1247
+ * 无 extraBody 时原样返回;有则允许覆盖同名顶层键。
1248
+ */
1249
+ declare function applyExtraBody<T extends object>(body: T, extraBody?: Record<string, unknown>): T;
1250
+ //#endregion
1251
+ //#region src/helpers/reasoning-level.d.ts
1252
+ declare const REASONING_LEVELS: readonly ["none", "minimal", "low", "medium", "high", "xhigh", "max"];
1253
+ declare const REASONING_LEVEL_SET: ReadonlySet<string>;
1254
+ type OpenAIReasoningEffort = ReasoningLevel;
1255
+ type MessagesThinkingConfig = {
1256
+ type: "disabled";
1257
+ } | {
1258
+ type: "enabled";
1259
+ budget_tokens: number;
1260
+ };
1261
+ type OllamaThinkValue = false | "low" | "medium" | "high";
1262
+ /** 若 level 不在 supported 集合内则抛 AIRequestError。 */
1263
+ declare function assertSupportedReasoningLevel(level: ReasoningLevel, supported: ReadonlySet<ReasoningLevel>, adapterKind: string): void;
1264
+ /** Responses API:`reasoning: { effort }` */
1265
+ declare function mapResponsesReasoning(level: ReasoningLevel): {
1266
+ effort: OpenAIReasoningEffort;
1267
+ };
1268
+ /** Chat Completions:顶层 `reasoning_effort` */
1269
+ declare function mapChatCompletionsReasoningEffort(level: ReasoningLevel): OpenAIReasoningEffort;
1270
+ /**
1271
+ * Messages thinking budget。
1272
+ * 基于 maxTokens 按比例推导,clamp 到 [1024, max(1024, maxTokens - 1)],
1273
+ * 满足 Anthropic budget_tokens < max_tokens。
1274
+ */
1275
+ declare function mapMessagesThinkingBudget(level: Exclude<ReasoningLevel, "none">, maxTokens: number): number;
1276
+ /** Messages API:`thinking` 字段 */
1277
+ declare function mapMessagesThinking(level: ReasoningLevel, maxTokens: number): MessagesThinkingConfig;
1278
+ /** Ollama:`think` 字段;minimal/xhigh/max 不支持 */
1279
+ declare function mapOllamaThink(level: ReasoningLevel): OllamaThinkValue;
1280
+ //#endregion
1203
1281
  //#region src/helpers/request-mapper.d.ts
1204
1282
  declare class NormalizedRequestMapper {
1205
1283
  readonly kind: string;
@@ -1228,5 +1306,5 @@ declare class NormalizedRequestMapper {
1228
1306
  private ensureBlocks;
1229
1307
  }
1230
1308
  //#endregion
1231
- 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 OpenProviderJsonStreamOptions, type OpenedProviderStream, type OutputItem, PROVIDER_ERROR_MESSAGE_MAX_LEN, PROVIDER_ERROR_RAW_BODY_THRESHOLD, type ProviderStreamBatch, type ProviderStreamBatchOptions, type ReasoningCompletedEvent, type ReasoningDeltaEvent, type ReasoningItem, type ReasoningStartedEvent, type ReplayItem, type ResponseAuxiliaryEvent, type ResponseCompletedEvent, type ResponseStartedEvent, type ResponseWarningEvent, ResponsesAdapter, type ResponsesAdapterOptions, type SseJsonEvent, 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, createChatCompletionsSseParser, createCompletionGate, createEventFactory, createNdjsonLineParser, createSseJsonParser, emitMalformedStreamWarning, extractProviderErrorMessage, extractText, imageBlock, iterateProviderStreamBatches, jsonBlock, mapReasoningVisibility, mapStopReason, measureJsonDepth, messageItem, normalizeRequest, opaqueBlock, opaqueItem, openProviderJsonStream, parseChatCompletionsDataLine, parseSseJsonFrame, providerHttpError, reasoningItem, replayFromOutput, splitLines, splitSSEFrames, syntheticStream, textBlock, toolCallItem, toolResultItem, usageFromAnthropicMessages, usageFromChatCompletions, usageFromOllama, usageFromOpenAIResponses, validateOpaqueReplayEnvelope, validateRequest, withMockStreaming };
1309
+ 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, type MessagesThinkingConfig, 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 OllamaThinkValue, type OpaqueEnvelopeResult, type OpaqueItem, type OpenAIReasoningEffort, type OpenProviderJsonStreamOptions, type OpenedProviderStream, type OutputItem, PROVIDER_ERROR_MESSAGE_MAX_LEN, PROVIDER_ERROR_RAW_BODY_THRESHOLD, type ProviderStreamBatch, type ProviderStreamBatchOptions, REASONING_LEVELS, REASONING_LEVEL_SET, type ReasoningCompletedEvent, type ReasoningDeltaEvent, type ReasoningItem, type ReasoningLevel, type ReasoningStartedEvent, type ReplayItem, type ResponseAuxiliaryEvent, type ResponseCompletedEvent, type ResponseStartedEvent, type ResponseWarningEvent, ResponsesAdapter, type ResponsesAdapterOptions, type SseJsonEvent, 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, applyExtraBody, assertMockRequest, assertOpaqueReplayEnvelope, assertSupportedReasoningLevel, assertValidRequest, blockToText, collectStream, contentBlocksToText, createAIClient, createChatCompletionsSseParser, createCompletionGate, createEventFactory, createNdjsonLineParser, createSseJsonParser, emitMalformedStreamWarning, extractProviderErrorMessage, extractText, imageBlock, iterateProviderStreamBatches, jsonBlock, mapChatCompletionsReasoningEffort, mapMessagesThinking, mapMessagesThinkingBudget, mapOllamaThink, mapReasoningVisibility, mapResponsesReasoning, mapStopReason, measureJsonDepth, mergeProviderHeaders, messageItem, normalizeRequest, opaqueBlock, opaqueItem, openProviderJsonStream, parseChatCompletionsDataLine, parseSseJsonFrame, providerHttpError, reasoningItem, replayFromOutput, splitLines, splitSSEFrames, syntheticStream, textBlock, toolCallItem, toolResultItem, usageFromAnthropicMessages, usageFromChatCompletions, usageFromOllama, usageFromOpenAIResponses, validateOpaqueReplayEnvelope, validateRequest, withMockStreaming };
1232
1310
  //# sourceMappingURL=index.d.mts.map
package/dist/index.mjs CHANGED
@@ -80,6 +80,15 @@ const TOOL_RESULT_OUTCOMES = /* @__PURE__ */ new Set([
80
80
  "rejected"
81
81
  ]);
82
82
  const INCLUDE_MODES = /* @__PURE__ */ new Set(["off", "best_effort"]);
83
+ const REASONING_LEVELS$1 = /* @__PURE__ */ new Set([
84
+ "none",
85
+ "minimal",
86
+ "low",
87
+ "medium",
88
+ "high",
89
+ "xhigh",
90
+ "max"
91
+ ]);
83
92
  function isRecord(value) {
84
93
  return typeof value === "object" && value !== null;
85
94
  }
@@ -248,6 +257,9 @@ function validateRequest(request) {
248
257
  message: "maxOutputTokens must be a positive integer"
249
258
  });
250
259
  }
260
+ if (request.reasoningLevel !== void 0) {
261
+ if (typeof request.reasoningLevel !== "string" || !REASONING_LEVELS$1.has(request.reasoningLevel)) pushIssue(issues, "reasoningLevel", "REASONING_LEVEL_INVALID", "reasoningLevel must be one of: none, minimal, low, medium, high, xhigh, max");
262
+ }
251
263
  if (request.include !== void 0) validateInclude(request.include, issues);
252
264
  if (request.metadata !== void 0) {
253
265
  if (!isRecord(request.metadata)) pushIssue(issues, "metadata", "METADATA_INVALID", "metadata must be an object");
@@ -1660,6 +1672,104 @@ function createCompletionGate() {
1660
1672
  };
1661
1673
  }
1662
1674
  //#endregion
1675
+ //#region src/helpers/provider-request-options.ts
1676
+ /**
1677
+ * Provider 请求 headers / body 扩展合并
1678
+ *
1679
+ * 供真实后端 adapter 构造选项 `headers` / `extraBody` 使用:
1680
+ * - headers:内置鉴权头为基,自定义后写覆盖
1681
+ * - extraBody:已构建 body 为基,额外字段浅层 spread,同名顶层键可覆盖
1682
+ */
1683
+ /** 合并内置 headers 与自定义 headers;自定义后写覆盖同名键。 */
1684
+ function mergeProviderHeaders(base, custom) {
1685
+ if (!custom) return base;
1686
+ return {
1687
+ ...base,
1688
+ ...custom
1689
+ };
1690
+ }
1691
+ /**
1692
+ * 将构造期 extraBody 浅层合并到已构建的 provider body。
1693
+ * 无 extraBody 时原样返回;有则允许覆盖同名顶层键。
1694
+ */
1695
+ function applyExtraBody(body, extraBody) {
1696
+ if (!extraBody) return body;
1697
+ return {
1698
+ ...body,
1699
+ ...extraBody
1700
+ };
1701
+ }
1702
+ //#endregion
1703
+ //#region src/helpers/reasoning-level.ts
1704
+ /**
1705
+ * Portable reasoningLevel → provider wire 字段映射
1706
+ *
1707
+ * 第一版只处理 level 枚举;budget/summary 等特化字段不在此层。
1708
+ * 无法映射的 level 抛 AIRequestError(UNSUPPORTED_REASONING_LEVEL)。
1709
+ */
1710
+ const REASONING_LEVELS = [
1711
+ "none",
1712
+ "minimal",
1713
+ "low",
1714
+ "medium",
1715
+ "high",
1716
+ "xhigh",
1717
+ "max"
1718
+ ];
1719
+ const REASONING_LEVEL_SET = new Set(REASONING_LEVELS);
1720
+ const MESSAGES_BUDGET_RATIOS = {
1721
+ minimal: .02,
1722
+ low: .1,
1723
+ medium: .3,
1724
+ high: .6,
1725
+ xhigh: .9,
1726
+ max: .95
1727
+ };
1728
+ const OLLAMA_SUPPORTED = /* @__PURE__ */ new Set([
1729
+ "none",
1730
+ "low",
1731
+ "medium",
1732
+ "high"
1733
+ ]);
1734
+ /** 若 level 不在 supported 集合内则抛 AIRequestError。 */
1735
+ function assertSupportedReasoningLevel(level, supported, adapterKind) {
1736
+ if (supported.has(level)) return;
1737
+ throw new AIRequestError(`reasoningLevel "${level}" is not supported by the ${adapterKind} adapter`, "UNSUPPORTED_REASONING_LEVEL");
1738
+ }
1739
+ /** Responses API:`reasoning: { effort }` */
1740
+ function mapResponsesReasoning(level) {
1741
+ return { effort: level };
1742
+ }
1743
+ /** Chat Completions:顶层 `reasoning_effort` */
1744
+ function mapChatCompletionsReasoningEffort(level) {
1745
+ return level;
1746
+ }
1747
+ /**
1748
+ * Messages thinking budget。
1749
+ * 基于 maxTokens 按比例推导,clamp 到 [1024, max(1024, maxTokens - 1)],
1750
+ * 满足 Anthropic budget_tokens < max_tokens。
1751
+ */
1752
+ function mapMessagesThinkingBudget(level, maxTokens) {
1753
+ const ratio = MESSAGES_BUDGET_RATIOS[level];
1754
+ const raw = Math.round(maxTokens * ratio);
1755
+ const upper = Math.max(1024, maxTokens - 1);
1756
+ return Math.min(Math.max(raw, 1024), upper);
1757
+ }
1758
+ /** Messages API:`thinking` 字段 */
1759
+ function mapMessagesThinking(level, maxTokens) {
1760
+ if (level === "none") return { type: "disabled" };
1761
+ return {
1762
+ type: "enabled",
1763
+ budget_tokens: mapMessagesThinkingBudget(level, maxTokens)
1764
+ };
1765
+ }
1766
+ /** Ollama:`think` 字段;minimal/xhigh/max 不支持 */
1767
+ function mapOllamaThink(level) {
1768
+ assertSupportedReasoningLevel(level, OLLAMA_SUPPORTED, "ollama");
1769
+ if (level === "none") return false;
1770
+ return level;
1771
+ }
1772
+ //#endregion
1663
1773
  //#region src/helpers/request-mapper.ts
1664
1774
  var NormalizedRequestMapper = class {
1665
1775
  kind;
@@ -1836,11 +1946,15 @@ var ResponsesAdapter = class extends AdapterBase {
1836
1946
  apiKey;
1837
1947
  baseUrl;
1838
1948
  fetchFn;
1949
+ headers;
1950
+ extraBody;
1839
1951
  constructor(options) {
1840
1952
  super();
1841
1953
  this.apiKey = options.apiKey;
1842
1954
  this.baseUrl = options.baseUrl ?? "https://api.openai.com/v1";
1843
1955
  this.fetchFn = options.fetch ?? globalThis.fetch;
1956
+ this.headers = options.headers;
1957
+ this.extraBody = options.extraBody;
1844
1958
  }
1845
1959
  buildRequest(request) {
1846
1960
  const input = [];
@@ -1918,7 +2032,8 @@ var ResponsesAdapter = class extends AdapterBase {
1918
2032
  if (request.temperature !== void 0) body.temperature = request.temperature;
1919
2033
  if (request.maxOutputTokens !== void 0) body.max_output_tokens = request.maxOutputTokens;
1920
2034
  if (request.metadata) body.metadata = request.metadata;
1921
- return body;
2035
+ if (request.reasoningLevel !== void 0) body.reasoning = mapResponsesReasoning(request.reasoningLevel);
2036
+ return applyExtraBody(body, this.extraBody);
1922
2037
  }
1923
2038
  async *runStream(providerRequest, factory, request) {
1924
2039
  const auxiliary = this.createAuxiliaryState(request);
@@ -1926,10 +2041,10 @@ var ResponsesAdapter = class extends AdapterBase {
1926
2041
  const { reader } = await openProviderJsonStream({
1927
2042
  fetchFn: this.fetchFn,
1928
2043
  url: `${this.baseUrl}/responses`,
1929
- headers: {
2044
+ headers: mergeProviderHeaders({
1930
2045
  "Content-Type": "application/json",
1931
2046
  Authorization: `Bearer ${this.apiKey}`
1932
- },
2047
+ }, this.headers),
1933
2048
  body: providerRequest,
1934
2049
  signal: request.signal
1935
2050
  });
@@ -2240,12 +2355,16 @@ var MessagesAdapter = class extends AdapterBase {
2240
2355
  apiVersion;
2241
2356
  baseUrl;
2242
2357
  fetchFn;
2358
+ headers;
2359
+ extraBody;
2243
2360
  constructor(options) {
2244
2361
  super();
2245
2362
  this.apiKey = options.apiKey;
2246
2363
  this.apiVersion = options.apiVersion ?? "2023-06-01";
2247
2364
  this.baseUrl = options.baseUrl ?? "https://api.anthropic.com/v1";
2248
2365
  this.fetchFn = options.fetch ?? globalThis.fetch;
2366
+ this.headers = options.headers;
2367
+ this.extraBody = options.extraBody;
2249
2368
  }
2250
2369
  buildRequest(request) {
2251
2370
  const messages = [];
@@ -2351,7 +2470,8 @@ var MessagesAdapter = class extends AdapterBase {
2351
2470
  })
2352
2471
  });
2353
2472
  if (request.temperature !== void 0) body.temperature = request.temperature;
2354
- return body;
2473
+ if (request.reasoningLevel !== void 0) body.thinking = mapMessagesThinking(request.reasoningLevel, body.max_tokens);
2474
+ return applyExtraBody(body, this.extraBody);
2355
2475
  }
2356
2476
  async *runStream(providerRequest, factory, request) {
2357
2477
  const auxiliary = this.createAuxiliaryState(request);
@@ -2360,11 +2480,11 @@ var MessagesAdapter = class extends AdapterBase {
2360
2480
  const { reader, headers } = await openProviderJsonStream({
2361
2481
  fetchFn: this.fetchFn,
2362
2482
  url: `${this.baseUrl}/messages`,
2363
- headers: {
2483
+ headers: mergeProviderHeaders({
2364
2484
  "Content-Type": "application/json",
2365
2485
  "x-api-key": this.apiKey,
2366
2486
  "anthropic-version": this.apiVersion
2367
- },
2487
+ }, this.headers),
2368
2488
  body: providerRequest,
2369
2489
  signal: request.signal
2370
2490
  });
@@ -2639,11 +2759,15 @@ var ChatCompletionsAdapter = class extends AdapterBase {
2639
2759
  apiKey;
2640
2760
  baseUrl;
2641
2761
  fetchFn;
2762
+ headers;
2763
+ extraBody;
2642
2764
  constructor(options) {
2643
2765
  super();
2644
2766
  this.apiKey = options.apiKey;
2645
2767
  this.baseUrl = options.baseUrl ?? "https://api.openai.com/v1";
2646
2768
  this.fetchFn = options.fetch ?? globalThis.fetch;
2769
+ this.headers = options.headers;
2770
+ this.extraBody = options.extraBody;
2647
2771
  }
2648
2772
  buildRequest(request) {
2649
2773
  const messages = [];
@@ -2737,7 +2861,8 @@ var ChatCompletionsAdapter = class extends AdapterBase {
2737
2861
  if (request.temperature !== void 0) body.temperature = request.temperature;
2738
2862
  if (request.maxOutputTokens !== void 0) body.max_tokens = request.maxOutputTokens;
2739
2863
  if (request.metadata) body.metadata = request.metadata;
2740
- return body;
2864
+ if (request.reasoningLevel !== void 0) body.reasoning_effort = mapChatCompletionsReasoningEffort(request.reasoningLevel);
2865
+ return applyExtraBody(body, this.extraBody);
2741
2866
  }
2742
2867
  async *runStream(providerRequest, factory, request) {
2743
2868
  const auxiliary = this.createAuxiliaryState(request);
@@ -2745,10 +2870,10 @@ var ChatCompletionsAdapter = class extends AdapterBase {
2745
2870
  const { reader } = await openProviderJsonStream({
2746
2871
  fetchFn: this.fetchFn,
2747
2872
  url: `${this.baseUrl}/chat/completions`,
2748
- headers: {
2873
+ headers: mergeProviderHeaders({
2749
2874
  "Content-Type": "application/json",
2750
2875
  Authorization: `Bearer ${this.apiKey}`
2751
- },
2876
+ }, this.headers),
2752
2877
  body: providerRequest,
2753
2878
  signal: request.signal
2754
2879
  });
@@ -2974,11 +3099,15 @@ var OllamaAdapter = class extends AdapterBase {
2974
3099
  baseUrl;
2975
3100
  apiKey;
2976
3101
  fetchFn;
3102
+ headers;
3103
+ extraBody;
2977
3104
  constructor(options = {}) {
2978
3105
  super();
2979
3106
  this.baseUrl = options.baseUrl ?? "http://localhost:11434";
2980
3107
  this.apiKey = options.apiKey;
2981
3108
  this.fetchFn = options.fetch ?? globalThis.fetch;
3109
+ this.headers = options.headers;
3110
+ this.extraBody = options.extraBody;
2982
3111
  }
2983
3112
  buildRequest(request) {
2984
3113
  const messages = [];
@@ -3076,7 +3205,8 @@ var OllamaAdapter = class extends AdapterBase {
3076
3205
  if (request.temperature !== void 0) body.options.temperature = request.temperature;
3077
3206
  if (request.maxOutputTokens !== void 0) body.options.num_predict = request.maxOutputTokens;
3078
3207
  }
3079
- return body;
3208
+ if (request.reasoningLevel !== void 0) body.think = mapOllamaThink(request.reasoningLevel);
3209
+ return applyExtraBody(body, this.extraBody);
3080
3210
  }
3081
3211
  async *runStream(providerRequest, factory, request) {
3082
3212
  const auxiliary = this.createAuxiliaryState(request);
@@ -3088,7 +3218,7 @@ var OllamaAdapter = class extends AdapterBase {
3088
3218
  const { reader } = await openProviderJsonStream({
3089
3219
  fetchFn: this.fetchFn,
3090
3220
  url: `${this.baseUrl}/api/chat`,
3091
- headers,
3221
+ headers: mergeProviderHeaders(headers, this.headers),
3092
3222
  body: providerRequest,
3093
3223
  signal: request.signal
3094
3224
  });
@@ -3257,7 +3387,7 @@ var MockAdapter = class extends AdapterBase {
3257
3387
  }
3258
3388
  async buildRequest(request) {
3259
3389
  const turnIndex = this.cursor;
3260
- const context = this.buildHandlerContext(turnIndex, request.signal);
3390
+ const context = this.buildHandlerContext(turnIndex, request);
3261
3391
  const remainingPendingToolCalls = consumePendingToolCalls(this.pendingToolCalls, request.input);
3262
3392
  const handlerResult = this.handler(request, context);
3263
3393
  this.cursor += 1;
@@ -3396,7 +3526,7 @@ var MockAdapter = class extends AdapterBase {
3396
3526
  rawResponseId: completion.rawResponseId
3397
3527
  }, factory);
3398
3528
  }
3399
- buildHandlerContext(turnIndex, signal) {
3529
+ buildHandlerContext(turnIndex, request) {
3400
3530
  return {
3401
3531
  turnIndex,
3402
3532
  previousReplay: this.previousReplay.map(cloneItem),
@@ -3406,7 +3536,8 @@ var MockAdapter = class extends AdapterBase {
3406
3536
  replay: record.replay.map(cloneItem),
3407
3537
  toolCalls: record.toolCalls.map(cloneItem)
3408
3538
  })),
3409
- signal
3539
+ signal: request.signal,
3540
+ reasoningLevel: request.reasoningLevel
3410
3541
  };
3411
3542
  }
3412
3543
  };
@@ -3632,6 +3763,6 @@ function cloneItem(item) {
3632
3763
  return structuredClone(item);
3633
3764
  }
3634
3765
  //#endregion
3635
- export { AIError, AIMappingError, AIProviderError, AIRequestError, AIStreamError, AdapterAuxiliaryState, AdapterBase, AuxiliaryCollector, ChatCompletionsAdapter, IncrementalStreamParser, MAX_OPAQUE_JSON_DEPTH, MAX_OPAQUE_PAYLOAD_BYTES, MessagesAdapter, MockAdapter, NormalizedRequestMapper, OllamaAdapter, PROVIDER_ERROR_MESSAGE_MAX_LEN, PROVIDER_ERROR_RAW_BODY_THRESHOLD, ResponsesAdapter, WarningCode, aggregateEvents, assertMockRequest, assertOpaqueReplayEnvelope, assertValidRequest, blockToText, collectStream, contentBlocksToText, createAIClient, createChatCompletionsSseParser, createCompletionGate, createEventFactory, createNdjsonLineParser, createSseJsonParser, emitMalformedStreamWarning, extractProviderErrorMessage, extractText, imageBlock, iterateProviderStreamBatches, jsonBlock, mapReasoningVisibility, mapStopReason, measureJsonDepth, messageItem, normalizeRequest, opaqueBlock, opaqueItem, openProviderJsonStream, parseChatCompletionsDataLine, parseSseJsonFrame, providerHttpError, reasoningItem, replayFromOutput, splitLines, splitSSEFrames, syntheticStream, textBlock, toolCallItem, toolResultItem, usageFromAnthropicMessages, usageFromChatCompletions, usageFromOllama, usageFromOpenAIResponses, validateOpaqueReplayEnvelope, validateRequest, withMockStreaming };
3766
+ export { AIError, AIMappingError, AIProviderError, AIRequestError, AIStreamError, AdapterAuxiliaryState, AdapterBase, AuxiliaryCollector, ChatCompletionsAdapter, IncrementalStreamParser, MAX_OPAQUE_JSON_DEPTH, MAX_OPAQUE_PAYLOAD_BYTES, MessagesAdapter, MockAdapter, NormalizedRequestMapper, OllamaAdapter, PROVIDER_ERROR_MESSAGE_MAX_LEN, PROVIDER_ERROR_RAW_BODY_THRESHOLD, REASONING_LEVELS, REASONING_LEVEL_SET, ResponsesAdapter, WarningCode, aggregateEvents, applyExtraBody, assertMockRequest, assertOpaqueReplayEnvelope, assertSupportedReasoningLevel, assertValidRequest, blockToText, collectStream, contentBlocksToText, createAIClient, createChatCompletionsSseParser, createCompletionGate, createEventFactory, createNdjsonLineParser, createSseJsonParser, emitMalformedStreamWarning, extractProviderErrorMessage, extractText, imageBlock, iterateProviderStreamBatches, jsonBlock, mapChatCompletionsReasoningEffort, mapMessagesThinking, mapMessagesThinkingBudget, mapOllamaThink, mapReasoningVisibility, mapResponsesReasoning, mapStopReason, measureJsonDepth, mergeProviderHeaders, messageItem, normalizeRequest, opaqueBlock, opaqueItem, openProviderJsonStream, parseChatCompletionsDataLine, parseSseJsonFrame, providerHttpError, reasoningItem, replayFromOutput, splitLines, splitSSEFrames, syntheticStream, textBlock, toolCallItem, toolResultItem, usageFromAnthropicMessages, usageFromChatCompletions, usageFromOllama, usageFromOpenAIResponses, validateOpaqueReplayEnvelope, validateRequest, withMockStreaming };
3636
3767
 
3637
3768
  //# sourceMappingURL=index.mjs.map