@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/.github/workflows/publish.yml +56 -0
- package/README.md +32 -4
- package/dist/index.d.mts +133 -32
- package/dist/index.mjs +209 -52
- package/dist/index.mjs.map +1 -1
- package/package.json +9 -1
- package/src/adapters/chat-completions.ts +24 -5
- package/src/adapters/messages.ts +24 -7
- package/src/adapters/mock.ts +7 -3
- package/src/adapters/ollama.ts +19 -2
- package/src/adapters/responses.ts +189 -55
- package/src/core/validation.ts +13 -0
- package/src/helpers/index.ts +14 -0
- package/src/helpers/provider-request-options.ts +25 -0
- package/src/helpers/reasoning-level.ts +85 -0
- package/src/types/index.ts +1 -1
- package/src/types/request.ts +11 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@codehz/ai",
|
|
3
|
-
"version": "0.4.
|
|
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
|
-
|
|
378
|
-
|
|
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
|
});
|
package/src/adapters/messages.ts
CHANGED
|
@@ -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
|
-
|
|
398
|
-
|
|
399
|
-
|
|
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
|
});
|
package/src/adapters/mock.ts
CHANGED
|
@@ -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
|
|
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,
|
|
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
|
}
|
package/src/adapters/ollama.ts
CHANGED
|
@@ -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
|
-
|
|
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
|
});
|