@codehz/ai 0.4.3 → 0.4.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,6 +1,6 @@
1
1
  {
2
2
  "name": "@codehz/ai",
3
- "version": "0.4.3",
3
+ "version": "0.4.5",
4
4
  "type": "module",
5
5
  "module": "dist/index.mjs",
6
6
  "exports": {
@@ -9,6 +9,14 @@
9
9
  "types": "./dist/index.d.mts"
10
10
  }
11
11
  },
12
+ "repository": {
13
+ "type": "git",
14
+ "url": "git+https://github.com/codehz/nano-ai.git"
15
+ },
16
+ "publishConfig": {
17
+ "access": "public",
18
+ "registry": "https://registry.npmjs.org/"
19
+ },
12
20
  "scripts": {
13
21
  "typecheck": "tsc --noEmit",
14
22
  "lint": "oxlint",
@@ -27,6 +27,9 @@ import {
27
27
  openProviderJsonStream,
28
28
  iterateProviderStreamBatches,
29
29
  createCompletionGate,
30
+ mergeProviderHeaders,
31
+ applyExtraBody,
32
+ mapChatCompletionsReasoningEffort,
30
33
  } from "../helpers/index.js";
31
34
 
32
35
  import type { NormalizedRequest, AIStreamEvent, EventFactory, OutputItem, FetchFn, StopReason } from "../index.js";
@@ -37,6 +40,10 @@ export type ChatCompletionsAdapterOptions = {
37
40
  apiKey: string;
38
41
  baseUrl?: string;
39
42
  fetch?: FetchFn;
43
+ /** 额外请求头;后写覆盖内置 Authorization / Content-Type */
44
+ headers?: Record<string, string>;
45
+ /** 额外 body 顶层字段;浅层合并,同名键可覆盖 */
46
+ extraBody?: Record<string, unknown>;
40
47
  };
41
48
 
42
49
  // ── Chat API 请求类型 ─────────────────────────────────────────
@@ -49,6 +56,8 @@ type ChatRequest = {
49
56
  metadata?: Record<string, string>;
50
57
  temperature?: number;
51
58
  max_tokens?: number;
59
+ /** Portable reasoningLevel → reasoning_effort */
60
+ reasoning_effort?: string;
52
61
  stream: true;
53
62
  n: 1;
54
63
  };
@@ -242,12 +251,16 @@ export class ChatCompletionsAdapter extends AdapterBase {
242
251
  private apiKey: string;
243
252
  private baseUrl: string;
244
253
  private fetchFn: FetchFn;
254
+ private headers: Record<string, string> | undefined;
255
+ private extraBody: Record<string, unknown> | undefined;
245
256
 
246
257
  constructor(options: ChatCompletionsAdapterOptions) {
247
258
  super();
248
259
  this.apiKey = options.apiKey;
249
260
  this.baseUrl = options.baseUrl ?? "https://api.openai.com/v1";
250
261
  this.fetchFn = options.fetch ?? globalThis.fetch;
262
+ this.headers = options.headers;
263
+ this.extraBody = options.extraBody;
251
264
  }
252
265
 
253
266
  // ── buildRequest ──────────────────────────────────────────
@@ -356,8 +369,11 @@ export class ChatCompletionsAdapter extends AdapterBase {
356
369
  if (request.temperature !== undefined) body.temperature = request.temperature;
357
370
  if (request.maxOutputTokens !== undefined) body.max_tokens = request.maxOutputTokens;
358
371
  if (request.metadata) body.metadata = request.metadata;
372
+ if (request.reasoningLevel !== undefined) {
373
+ body.reasoning_effort = mapChatCompletionsReasoningEffort(request.reasoningLevel);
374
+ }
359
375
 
360
- return body;
376
+ return applyExtraBody(body, this.extraBody);
361
377
  }
362
378
 
363
379
  // ── runStream ─────────────────────────────────────────────
@@ -373,10 +389,13 @@ export class ChatCompletionsAdapter extends AdapterBase {
373
389
  const { reader } = await openProviderJsonStream({
374
390
  fetchFn: this.fetchFn,
375
391
  url: `${this.baseUrl}/chat/completions`,
376
- headers: {
377
- "Content-Type": "application/json",
378
- Authorization: `Bearer ${this.apiKey}`,
379
- },
392
+ headers: mergeProviderHeaders(
393
+ {
394
+ "Content-Type": "application/json",
395
+ Authorization: `Bearer ${this.apiKey}`,
396
+ },
397
+ this.headers,
398
+ ),
380
399
  body: providerRequest,
381
400
  signal: request.signal,
382
401
  });
@@ -30,6 +30,9 @@ import {
30
30
  openProviderJsonStream,
31
31
  iterateProviderStreamBatches,
32
32
  createCompletionGate,
33
+ mergeProviderHeaders,
34
+ applyExtraBody,
35
+ mapMessagesThinking,
33
36
  } from "../helpers/index.js";
34
37
 
35
38
  import type { NormalizedRequest, AIStreamEvent, EventFactory, OutputItem, FetchFn } from "../index.js";
@@ -42,6 +45,10 @@ export type MessagesAdapterOptions = {
42
45
  baseUrl?: string;
43
46
  /** 可注入自定义 fetch 实现(用于测试/代理) */
44
47
  fetch?: FetchFn;
48
+ /** 额外请求头;后写覆盖内置 x-api-key / Content-Type / anthropic-version */
49
+ headers?: Record<string, string>;
50
+ /** 额外 body 顶层字段;浅层合并,同名键可覆盖 */
51
+ extraBody?: Record<string, unknown>;
45
52
  };
46
53
 
47
54
  // ── Messages API 请求类型 ────────────────────────────────────
@@ -54,7 +61,7 @@ type MessagesAPIRequest = {
54
61
  tools?: MessagesAPITool[];
55
62
  tool_choice?: { type: "auto" | "none" } | { type: "tool"; name: string };
56
63
  temperature?: number;
57
- thinking?: { type: "enabled"; budget_tokens: number };
64
+ thinking?: { type: "enabled"; budget_tokens: number } | { type: "disabled" };
58
65
  stream: true;
59
66
  };
60
67
 
@@ -243,6 +250,8 @@ export class MessagesAdapter extends AdapterBase {
243
250
  private apiVersion: string;
244
251
  private baseUrl: string;
245
252
  private fetchFn: FetchFn;
253
+ private headers: Record<string, string> | undefined;
254
+ private extraBody: Record<string, unknown> | undefined;
246
255
 
247
256
  constructor(options: MessagesAdapterOptions) {
248
257
  super();
@@ -250,6 +259,8 @@ export class MessagesAdapter extends AdapterBase {
250
259
  this.apiVersion = options.apiVersion ?? "2023-06-01";
251
260
  this.baseUrl = options.baseUrl ?? "https://api.anthropic.com/v1";
252
261
  this.fetchFn = options.fetch ?? globalThis.fetch;
262
+ this.headers = options.headers;
263
+ this.extraBody = options.extraBody;
253
264
  }
254
265
 
255
266
  // ── buildRequest ──────────────────────────────────────────
@@ -369,8 +380,11 @@ export class MessagesAdapter extends AdapterBase {
369
380
  });
370
381
 
371
382
  if (request.temperature !== undefined) body.temperature = request.temperature;
383
+ if (request.reasoningLevel !== undefined) {
384
+ body.thinking = mapMessagesThinking(request.reasoningLevel, body.max_tokens);
385
+ }
372
386
 
373
- return body;
387
+ return applyExtraBody(body, this.extraBody);
374
388
  }
375
389
 
376
390
  // ── runStream ─────────────────────────────────────────────
@@ -393,11 +407,14 @@ export class MessagesAdapter extends AdapterBase {
393
407
  const { reader, headers } = await openProviderJsonStream({
394
408
  fetchFn: this.fetchFn,
395
409
  url: `${this.baseUrl}/messages`,
396
- headers: {
397
- "Content-Type": "application/json",
398
- "x-api-key": this.apiKey,
399
- "anthropic-version": this.apiVersion,
400
- },
410
+ headers: mergeProviderHeaders(
411
+ {
412
+ "Content-Type": "application/json",
413
+ "x-api-key": this.apiKey,
414
+ "anthropic-version": this.apiVersion,
415
+ },
416
+ this.headers,
417
+ ),
401
418
  body: providerRequest,
402
419
  signal: request.signal,
403
420
  });
@@ -24,6 +24,7 @@ import type {
24
24
  MessageItem,
25
25
  NormalizedRequest,
26
26
  OutputItem,
27
+ ReasoningLevel,
27
28
  ReplayItem,
28
29
  StopReason,
29
30
  ToolCallItem,
@@ -70,6 +71,8 @@ export type MockHandlerContext = {
70
71
  history: readonly MockHistoryRecord[];
71
72
  /** 请求的 AbortSignal,handler 可检查 signal.aborted 提前退出。 */
72
73
  signal?: AbortSignal;
74
+ /** 当前请求的 portable reasoningLevel(若设置)。 */
75
+ reasoningLevel?: ReasoningLevel;
73
76
  };
74
77
 
75
78
  export type MockWarningStep = {
@@ -284,7 +287,7 @@ export class MockAdapter extends AdapterBase {
284
287
 
285
288
  protected async buildRequest(request: NormalizedRequest): Promise<MockProviderRequest> {
286
289
  const turnIndex = this.cursor;
287
- const context = this.buildHandlerContext(turnIndex, request.signal);
290
+ const context = this.buildHandlerContext(turnIndex, request);
288
291
  const remainingPendingToolCalls = consumePendingToolCalls(this.pendingToolCalls, request.input);
289
292
  const handlerResult = this.handler(request, context);
290
293
 
@@ -476,7 +479,7 @@ export class MockAdapter extends AdapterBase {
476
479
  );
477
480
  }
478
481
 
479
- private buildHandlerContext(turnIndex: number, signal?: AbortSignal): MockHandlerContext {
482
+ private buildHandlerContext(turnIndex: number, request: NormalizedRequest): MockHandlerContext {
480
483
  return {
481
484
  turnIndex,
482
485
  previousReplay: this.previousReplay.map(cloneItem),
@@ -486,7 +489,8 @@ export class MockAdapter extends AdapterBase {
486
489
  replay: record.replay.map(cloneItem),
487
490
  toolCalls: record.toolCalls.map(cloneItem),
488
491
  })),
489
- signal,
492
+ signal: request.signal,
493
+ reasoningLevel: request.reasoningLevel,
490
494
  };
491
495
  }
492
496
  }
@@ -34,6 +34,9 @@ import {
34
34
  openProviderJsonStream,
35
35
  iterateProviderStreamBatches,
36
36
  createCompletionGate,
37
+ mergeProviderHeaders,
38
+ applyExtraBody,
39
+ mapOllamaThink,
37
40
  } from "../helpers/index.js";
38
41
 
39
42
  import type { NormalizedRequest, AIStreamEvent, EventFactory, OutputItem, FetchFn, StopReason } from "../index.js";
@@ -47,6 +50,10 @@ export type OllamaAdapterOptions = {
47
50
  apiKey?: string;
48
51
  /** 可注入自定义 fetch 实现 */
49
52
  fetch?: FetchFn;
53
+ /** 额外请求头;后写覆盖内置 Content-Type / Authorization */
54
+ headers?: Record<string, string>;
55
+ /** 额外 body 顶层字段;浅层合并,同名键可覆盖 */
56
+ extraBody?: Record<string, unknown>;
50
57
  };
51
58
 
52
59
  // ── Ollama Chat API 类型 ──────────────────────────────────────
@@ -56,6 +63,8 @@ type OllamaChatRequest = {
56
63
  messages: OllamaMessage[];
57
64
  stream: true;
58
65
  tools?: OllamaTool[];
66
+ /** Portable reasoningLevel → think;minimal/xhigh 不支持 */
67
+ think?: boolean | "low" | "medium" | "high";
59
68
  options?: {
60
69
  temperature?: number;
61
70
  num_predict?: number;
@@ -151,12 +160,16 @@ export class OllamaAdapter extends AdapterBase {
151
160
  private baseUrl: string;
152
161
  private apiKey: string | undefined;
153
162
  private fetchFn: FetchFn;
163
+ private headers: Record<string, string> | undefined;
164
+ private extraBody: Record<string, unknown> | undefined;
154
165
 
155
166
  constructor(options: OllamaAdapterOptions = {}) {
156
167
  super();
157
168
  this.baseUrl = options.baseUrl ?? "http://localhost:11434";
158
169
  this.apiKey = options.apiKey;
159
170
  this.fetchFn = options.fetch ?? globalThis.fetch;
171
+ this.headers = options.headers;
172
+ this.extraBody = options.extraBody;
160
173
  }
161
174
 
162
175
  // ── buildRequest ──────────────────────────────────────────
@@ -291,7 +304,11 @@ export class OllamaAdapter extends AdapterBase {
291
304
  if (request.maxOutputTokens !== undefined) body.options.num_predict = request.maxOutputTokens;
292
305
  }
293
306
 
294
- return body;
307
+ if (request.reasoningLevel !== undefined) {
308
+ body.think = mapOllamaThink(request.reasoningLevel);
309
+ }
310
+
311
+ return applyExtraBody(body, this.extraBody);
295
312
  }
296
313
 
297
314
  // ── runStream ─────────────────────────────────────────────
@@ -326,7 +343,7 @@ export class OllamaAdapter extends AdapterBase {
326
343
  const { reader } = await openProviderJsonStream({
327
344
  fetchFn: this.fetchFn,
328
345
  url: `${this.baseUrl}/api/chat`,
329
- headers,
346
+ headers: mergeProviderHeaders(headers, this.headers),
330
347
  body: providerRequest,
331
348
  signal: request.signal,
332
349
  });