@agjs/tsforge 0.1.14 → 0.1.16
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 +2 -1
- package/scripts/analyze-malformed.ts +264 -0
- package/scripts/analyze-runs.ts +279 -0
- package/scripts/benchmark-catalog.ts +387 -0
- package/scripts/browser-check.ts +87 -0
- package/scripts/build-rule-docs.ts +122 -0
- package/scripts/build-rules-md.ts +129 -0
- package/scripts/cli-metrics.ts +203 -0
- package/scripts/coverage-check.ts +33 -0
- package/scripts/edit-benchmark.ts +314 -0
- package/scripts/eval-create.ts +48 -0
- package/scripts/eval-spec.ts +47 -0
- package/scripts/eval-sum.ts +79 -0
- package/scripts/gen-tests.ts +140 -0
- package/scripts/headless-build.ts +292 -0
- package/scripts/interactive-eval.ts +172 -0
- package/scripts/rejudge.ts +135 -0
- package/scripts/run-eval-todo.ts +59 -0
- package/scripts/smoke.ts +18 -0
- package/scripts/stub-check.ts +44 -0
- package/scripts/sweep-report.ts +76 -0
- package/scripts/sweep.ts +389 -0
- package/src/cli.ts +39 -1
- package/src/inference/inference.types.ts +20 -0
- package/src/inference/openai-compatible.ts +11 -34
- package/src/inference/request.ts +148 -0
- package/src/models-config.ts +13 -0
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
IChatMessage,
|
|
3
|
+
ICompleteOptions,
|
|
4
|
+
IOpenAICompatibleConfig,
|
|
5
|
+
ReasoningStyle,
|
|
6
|
+
} from "./inference.types";
|
|
7
|
+
import { PROVIDER_LIMITS } from "./inference.constants";
|
|
8
|
+
import { toWire } from "./wire";
|
|
9
|
+
|
|
10
|
+
/** Interpolate `${VAR}` references from `env` into a string (missing → ""). */
|
|
11
|
+
function interpolateEnv(
|
|
12
|
+
value: string,
|
|
13
|
+
env: Readonly<Record<string, string | undefined>>
|
|
14
|
+
): string {
|
|
15
|
+
return value.replace(
|
|
16
|
+
/\$\{([A-Za-z0-9_]+)\}/g,
|
|
17
|
+
(_m: string, name: string) => env[name] ?? ""
|
|
18
|
+
);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function style(cfg: IOpenAICompatibleConfig): ReasoningStyle {
|
|
22
|
+
return cfg.reasoning ?? "qwen";
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/** Provider-specific reasoning/thinking fields for the request body. */
|
|
26
|
+
function reasoningFields(
|
|
27
|
+
cfg: IOpenAICompatibleConfig,
|
|
28
|
+
opts: ICompleteOptions
|
|
29
|
+
): Record<string, unknown> {
|
|
30
|
+
switch (style(cfg)) {
|
|
31
|
+
case "qwen":
|
|
32
|
+
return {
|
|
33
|
+
...(opts.enableThinking === undefined
|
|
34
|
+
? {}
|
|
35
|
+
: { chat_template_kwargs: { enable_thinking: opts.enableThinking } }),
|
|
36
|
+
...(opts.thinkingTokenBudget === undefined
|
|
37
|
+
? {}
|
|
38
|
+
: { thinking_token_budget: opts.thinkingTokenBudget }),
|
|
39
|
+
};
|
|
40
|
+
case "deepseek":
|
|
41
|
+
return {
|
|
42
|
+
...(opts.enableThinking === undefined
|
|
43
|
+
? {}
|
|
44
|
+
: {
|
|
45
|
+
thinking: {
|
|
46
|
+
type: opts.enableThinking ? "enabled" : "disabled",
|
|
47
|
+
},
|
|
48
|
+
}),
|
|
49
|
+
...(cfg.reasoningEffort === undefined
|
|
50
|
+
? {}
|
|
51
|
+
: { reasoning_effort: cfg.reasoningEffort }),
|
|
52
|
+
};
|
|
53
|
+
case "openai":
|
|
54
|
+
return cfg.reasoningEffort === undefined
|
|
55
|
+
? {}
|
|
56
|
+
: { reasoning_effort: cfg.reasoningEffort };
|
|
57
|
+
case "none":
|
|
58
|
+
return {};
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/** The output-token cap field — o-series renamed `max_tokens` → `max_completion_tokens`. */
|
|
63
|
+
function tokenCapField(cfg: IOpenAICompatibleConfig): Record<string, number> {
|
|
64
|
+
const max = cfg.maxTokens ?? PROVIDER_LIMITS.maxTokens;
|
|
65
|
+
|
|
66
|
+
return style(cfg) === "openai"
|
|
67
|
+
? { max_completion_tokens: max }
|
|
68
|
+
: { max_tokens: max };
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/** Tool-choice clamped for provider constraints: DeepSeek's thinking mode rejects
|
|
72
|
+
* `tool_choice: "required"`, so downgrade it to `"auto"` there. */
|
|
73
|
+
function toolChoiceFor(
|
|
74
|
+
cfg: IOpenAICompatibleConfig,
|
|
75
|
+
requested: "auto" | "required" | "none"
|
|
76
|
+
): "auto" | "required" | "none" {
|
|
77
|
+
if (style(cfg) === "deepseek" && requested === "required") {
|
|
78
|
+
return "auto";
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
return requested;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/** Build the request body object (pure). Field order keeps the qwen default
|
|
85
|
+
* byte-for-byte identical; `extraBody` is merged last so it can override
|
|
86
|
+
* anything for a fully custom provider. */
|
|
87
|
+
export function buildRequestBody(
|
|
88
|
+
cfg: IOpenAICompatibleConfig,
|
|
89
|
+
messages: IChatMessage[],
|
|
90
|
+
opts: ICompleteOptions,
|
|
91
|
+
streaming: boolean
|
|
92
|
+
): Record<string, unknown> {
|
|
93
|
+
// o-series rejects `temperature` entirely; everywhere else send it only when set.
|
|
94
|
+
const omitTemperature =
|
|
95
|
+
style(cfg) === "openai" || opts.temperature === undefined;
|
|
96
|
+
|
|
97
|
+
return {
|
|
98
|
+
model: cfg.model,
|
|
99
|
+
messages: messages.map(toWire),
|
|
100
|
+
...tokenCapField(cfg),
|
|
101
|
+
...(omitTemperature ? {} : { temperature: opts.temperature }),
|
|
102
|
+
...(cfg.repetitionPenalty === undefined
|
|
103
|
+
? {}
|
|
104
|
+
: { repetition_penalty: cfg.repetitionPenalty }),
|
|
105
|
+
...(opts.tools === undefined
|
|
106
|
+
? {}
|
|
107
|
+
: {
|
|
108
|
+
tools: opts.tools,
|
|
109
|
+
tool_choice: toolChoiceFor(cfg, opts.toolChoice ?? "auto"),
|
|
110
|
+
}),
|
|
111
|
+
...reasoningFields(cfg, opts),
|
|
112
|
+
...(streaming
|
|
113
|
+
? { stream: true, stream_options: { include_usage: true } }
|
|
114
|
+
: {}),
|
|
115
|
+
...(cfg.extraBody ?? {}),
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/** Build request headers: JSON + Bearer auth (when a key is set) + any
|
|
120
|
+
* `extraHeaders` (with `${VAR}` interpolation), which can override the defaults. */
|
|
121
|
+
export function buildRequestHeaders(
|
|
122
|
+
cfg: IOpenAICompatibleConfig,
|
|
123
|
+
env: Readonly<Record<string, string | undefined>> = process.env
|
|
124
|
+
): Record<string, string> {
|
|
125
|
+
const headers: Record<string, string> = {
|
|
126
|
+
"content-type": "application/json",
|
|
127
|
+
};
|
|
128
|
+
|
|
129
|
+
if (cfg.apiKey !== undefined) {
|
|
130
|
+
headers.authorization = `Bearer ${cfg.apiKey}`;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
for (const [key, value] of Object.entries(cfg.extraHeaders ?? {})) {
|
|
134
|
+
headers[key] = interpolateEnv(value, env);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
return headers;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/** Normalize the chat-completions URL: trim trailing slashes and don't
|
|
141
|
+
* double-append when the baseUrl already ends with the path. */
|
|
142
|
+
export function chatCompletionsUrl(baseUrl: string): string {
|
|
143
|
+
const trimmed = baseUrl.replace(/\/+$/, "");
|
|
144
|
+
|
|
145
|
+
return trimmed.endsWith("/chat/completions")
|
|
146
|
+
? trimmed
|
|
147
|
+
: `${trimmed}/chat/completions`;
|
|
148
|
+
}
|
package/src/models-config.ts
CHANGED
|
@@ -3,6 +3,7 @@ import { join } from "node:path";
|
|
|
3
3
|
import { mkdir, readFile, writeFile, chmod } from "node:fs/promises";
|
|
4
4
|
import { isRecord } from "./lib/guards";
|
|
5
5
|
import { PROVIDER_DEFAULTS } from "./inference/inference.constants";
|
|
6
|
+
import type { ReasoningStyle } from "./inference/inference.types";
|
|
6
7
|
|
|
7
8
|
/**
|
|
8
9
|
* The model registry — `~/.tsforge/models.json`, the central place a user
|
|
@@ -28,6 +29,18 @@ export interface IModelEntry {
|
|
|
28
29
|
thinking?: boolean;
|
|
29
30
|
/** Per-response token cap override. */
|
|
30
31
|
maxTokens?: number;
|
|
32
|
+
/** Provider reasoning dialect: how thinking/reasoning is expressed on the wire.
|
|
33
|
+
* `qwen` (default) | `deepseek` | `openai` | `none`. Set `deepseek` for the
|
|
34
|
+
* DeepSeek API, `openai` for OpenAI o-series. */
|
|
35
|
+
reasoning?: ReasoningStyle;
|
|
36
|
+
/** Reasoning effort for `deepseek`/`openai` styles. */
|
|
37
|
+
reasoningEffort?: "low" | "medium" | "high";
|
|
38
|
+
/** Arbitrary fields merged into the request body (override built-ins) — the
|
|
39
|
+
* escape hatch for any provider-specific param. */
|
|
40
|
+
extraBody?: Record<string, unknown>;
|
|
41
|
+
/** Arbitrary request headers (e.g. a non-Bearer auth scheme); `${VAR}` values
|
|
42
|
+
* are interpolated from the environment. */
|
|
43
|
+
extraHeaders?: Record<string, string>;
|
|
31
44
|
}
|
|
32
45
|
|
|
33
46
|
export interface IModelsConfig {
|