fix(release): repair npm manifests and MiniMax thinking defaults

This commit is contained in:
Ma
2026-07-07 12:35:33 +08:00
parent 84b61bd4d1
commit 15fafc95d3
18 changed files with 138 additions and 53 deletions
+1
View File
@@ -0,0 +1 @@
link-workspace-packages=true
+8
View File
@@ -1,5 +1,13 @@
# Changelog
## v1.6.3
### Hotfix
- 修复 `@actalk/inkos@1.6.2` / `@actalk/inkos-studio@1.6.2` 发布到 npm 时 registry manifest 泄漏 `workspace:*` 的问题;Windows / npm 全局升级请直接安装 `1.6.3` 或更新到 `latest`
- 发布校验现在会拒绝 publishable manifest 中的 `workspace:` 依赖,避免同类安装错误复发
- MiniMax 官方 OpenAI-compatible 接入新增 `MiniMax-M3` 模型卡,并对 `MiniMax-M3*` 默认发送 `thinking: { "type": "disabled" }`,减少接口默认返回 thinking 内容的问题
## v1.6.2
### Release Focus
+1 -1
View File
@@ -57,7 +57,7 @@ Keep commits atomic — one logical change per commit. Split new files, interfac
- Immutable patterns: `{ ...obj, key: value }` over mutation
- Functions < 50 lines, files < 800 lines
- Errors must surface, not be swallowed (`catch { }` without re-throw needs a comment)
- `workspace:*` stays in source `package.json` — the CI pipeline handles version replacement at publish time
- Publishable package manifests must use registry-installable internal versions, not `workspace:*`; `pnpm` links local packages through the workspace config during development.
## Adding a CLI Command
+2
View File
@@ -165,6 +165,8 @@ Open Studio, then go to **Model Settings**:
Studio uses project service settings and `.inkos/secrets.json`. It may show env-detection hints, but env files do not override the Studio-selected service/model/base URL/API key.
MiniMax uses the official OpenAI-compatible `/v1/chat/completions` endpoint. InkOS disables returned thinking by default for `MiniMax-M3*`; M2.x thinking cannot be disabled by the upstream service.
**Option 2: CLI / daemon / deployment env config**
```bash
+2
View File
@@ -130,6 +130,8 @@ Studio を開き、**モデル設定**へ進みます:
Studio はプロジェクトのサービス設定と `.inkos/secrets.json` を使います。env が検出されてもヒントとして表示するだけで、Studio で選んだ service / model / base URL / API Key を上書きしません。
MiniMax は公式 OpenAI-compatible `/v1/chat/completions` エンドポイントを使用します。InkOS は `MiniMax-M3*` の thinking 返却をデフォルトで無効化します。M2.x の thinking は上流サービス側の制限により無効化できません。
**方法2CLI / daemon / デプロイ環境の env 設定**
```bash
+1 -1
View File
@@ -273,7 +273,7 @@ inkos doctor
| `legacy-env` | 旧 env 模式:兼容老项目的纯 `.env` 配置 |
如果服务测试失败,优先检查服务商、模型和协议是否匹配。Google Gemini 的 AI Studio API Key 可用于 Gemini OpenAI-compatible endpointInkOS 会自动禁用 Google 不支持的 OpenAI `store` 参数。MiniMax / MiniMax CodingPlan 默认走官方 OpenAI-compatible `/v1/chat/completions`,并优先使用可工作的非流式 transport,避免流式返回 usage 但无正文的问题。
如果服务测试失败,优先检查服务商、模型和协议是否匹配。Google Gemini 的 AI Studio API Key 可用于 Gemini OpenAI-compatible endpointInkOS 会自动禁用 Google 不支持的 OpenAI `store` 参数。MiniMax 默认走官方 OpenAI-compatible `/v1/chat/completions`,并优先使用可工作的非流式 transport,避免流式返回 usage 但无正文的问题`MiniMax-M3*` 会默认关闭 thinking 返回,M2.x thinking 由上游限制无法关闭
### LLM 配置更新
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "inkos",
"version": "1.6.2",
"version": "1.6.3",
"private": true,
"description": "Story Creation AI Agent for long-form and short fiction, scripts, interactive games, and IP content",
"keywords": [
+3 -3
View File
@@ -1,6 +1,6 @@
{
"name": "@actalk/inkos",
"version": "1.6.2",
"version": "1.6.3",
"description": "Story Creation AI Agent CLI for long-form fiction, InkOS Short, fan works, continuations, covers, and interactive worlds.",
"keywords": [
"story-creation-ai-agent",
@@ -49,8 +49,8 @@
"typecheck": "tsc --noEmit"
},
"dependencies": {
"@actalk/inkos-core": "workspace:*",
"@actalk/inkos-studio": "workspace:*",
"@actalk/inkos-core": "1.6.3",
"@actalk/inkos-studio": "1.6.3",
"commander": "^13.0.0",
"dotenv": "^16.4.0",
"epub-gen-memory": "^1.0.10",
@@ -117,11 +117,13 @@ describe.sequential("publish packaging", () => {
}
});
it("keeps source CLI dependencies linked through the workspace protocol", async () => {
it("keeps source publish dependencies registry-installable", async () => {
const cliPackageJson = await sourceCliPackageJsonPromise;
const studioPackageJson = await sourceStudioPackageJsonPromise;
expect(cliPackageJson.dependencies["@actalk/inkos-core"]).toBe("workspace:*");
expect(cliPackageJson.dependencies["@actalk/inkos-studio"]).toBe("workspace:*");
expect(cliPackageJson.dependencies["@actalk/inkos-core"]).not.toMatch(/^workspace:/);
expect(cliPackageJson.dependencies["@actalk/inkos-studio"]).not.toMatch(/^workspace:/);
expect(studioPackageJson.dependencies["@actalk/inkos-core"]).not.toMatch(/^workspace:/);
});
it("verifies publishable manifests before npm publish runs", async () => {
@@ -138,7 +140,7 @@ describe.sequential("publish packaging", () => {
);
});
it("allows source workspace protocol manifests when they normalize cleanly for publish", async () => {
it("rejects workspace protocol in source publish manifests", async () => {
const tempRoot = await mkdtemp(join(tmpdir(), "inkos-publish-verify-pass-"));
const tempPackagesDir = join(tempRoot, "packages");
const tempCoreDir = join(tempPackagesDir, "core");
@@ -182,7 +184,7 @@ describe.sequential("publish packaging", () => {
encoding: "utf-8",
stdio: "pipe",
},
)).not.toThrow();
)).toThrow(/workspace protocol is not allowed/);
} finally {
await rm(tempRoot, { recursive: true, force: true });
}
@@ -231,7 +233,7 @@ describe.sequential("publish packaging", () => {
encoding: "utf-8",
stdio: "pipe",
},
)).toThrow(/normalizes to 0\.5\.0, expected 0\.5\.1/);
)).toThrow(/workspace protocol is not allowed/);
} finally {
await rm(tempRoot, { recursive: true, force: true });
}
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@actalk/inkos-core",
"version": "1.6.2",
"version": "1.6.3",
"description": "InkOS core engine for story creation agents: governed context, writing, audit, revision, covers, and interactive worlds",
"keywords": [
"story-creation-ai-agent",
@@ -0,0 +1,77 @@
import { describe, expect, it, vi } from "vitest";
import { chatCompletion, createLLMClient } from "../llm/provider.js";
const { fetchCalls } = vi.hoisted(() => ({
fetchCalls: [] as Array<{ url: string; init: RequestInit; body: Record<string, unknown> }>,
}));
vi.mock("../utils/proxy-fetch.js", () => ({
fetchWithProxy: vi.fn(async (url: string, init: RequestInit) => {
fetchCalls.push({
url,
init,
body: JSON.parse(String(init.body ?? "{}")),
});
return {
ok: true,
json: async () => ({
choices: [{ message: { content: "ok" } }],
usage: { prompt_tokens: 1, completion_tokens: 1, total_tokens: 2 },
}),
} as Response;
}),
}));
vi.mock("@mariozechner/pi-ai", () => ({
completeSimple: vi.fn(async () => {
throw new Error("MiniMax OpenAI-compatible requests must use InkOS native transport");
}),
streamSimple: vi.fn(async function* () {
throw new Error("MiniMax OpenAI-compatible requests must use InkOS native transport");
}),
}));
function minimaxClient(model: string) {
return createLLMClient({
provider: "openai",
service: "minimax",
model,
apiKey: "sk-test",
apiFormat: "chat",
stream: false,
temperature: 0.9,
thinkingBudget: 0,
extra: {},
} as never);
}
describe("MiniMax thinking defaults", () => {
it("disables MiniMax-M3 thinking by default on the OpenAI-compatible endpoint", async () => {
fetchCalls.length = 0;
const client = minimaxClient("MiniMax-M3");
const result = await chatCompletion(client, "MiniMax-M3", [
{ role: "user", content: "hi" },
], { retry: false });
expect(result.content).toBe("ok");
expect(fetchCalls).toHaveLength(1);
expect(fetchCalls[0]!.url).toBe("https://api.minimaxi.com/v1/chat/completions");
expect(fetchCalls[0]!.body).toMatchObject({
model: "MiniMax-M3",
thinking: { type: "disabled" },
});
});
it("does not send unsupported thinking controls to MiniMax-M2.x models", async () => {
fetchCalls.length = 0;
const client = minimaxClient("MiniMax-M2.7");
await chatCompletion(client, "MiniMax-M2.7", [
{ role: "user", content: "hi" },
], { retry: false });
expect(fetchCalls).toHaveLength(1);
expect(fetchCalls[0]!.body).not.toHaveProperty("thinking");
});
});
@@ -31,10 +31,11 @@ describe("service-presets regression", () => {
expect(preset!.api).toBe("openai-completions");
});
it("has knownModels with all 7 MiniMax models", () => {
it("has knownModels with current MiniMax chat models", () => {
const preset = resolveServicePreset("minimax");
expect(preset!.knownModels).toBeDefined();
expect(preset!.knownModels).toHaveLength(7);
expect(preset!.knownModels).toHaveLength(8);
expect(preset!.knownModels).toContain("MiniMax-M3");
expect(preset!.knownModels).toContain("MiniMax-M2.7");
expect(preset!.knownModels).toContain("MiniMax-M2.7-highspeed");
expect(preset!.knownModels).toContain("MiniMax-M2.5");
@@ -62,6 +63,7 @@ describe("service-presets regression", () => {
it("returns provider bank models for minimax (B8 升级:provider.models 替代 preset.knownModels)", async () => {
const models = await listModelsForService("minimax");
expect(models.length).toBeGreaterThanOrEqual(7);
expect(models.some((m) => m.id === "MiniMax-M3")).toBe(true);
expect(models.some((m) => m.id === "MiniMax-M2.7")).toBe(true);
});
+14
View File
@@ -616,6 +616,9 @@ async function withTransientLLMRetry<T>(
}
function shouldUseNativeCustomTransport(client: LLMClient): boolean {
if (client.service === "minimax" && client.provider === "openai") {
return true;
}
if (client.service === "kkaiapi" && client.provider === "openai") {
return true;
}
@@ -650,6 +653,16 @@ function buildCustomHeaders(client: LLMClient): Record<string, string> {
}) ?? { "Content-Type": "application/json" };
}
function defaultOpenAIChatExtra(client: LLMClient, model: string): Record<string, unknown> {
if (
client.service === "minimax"
&& /^minimax-m3(?:$|[-_.])/i.test(model)
) {
return { thinking: { type: "disabled" } };
}
return {};
}
function sanitizeHeaderApiKey(apiKey: string | undefined): string {
const trimmed = apiKey?.trim() ?? "";
if (!trimmed) return "";
@@ -1041,6 +1054,7 @@ async function chatCompletionViaCustomOpenAICompatible(
stream: client.stream,
temperature: resolved.temperature,
max_tokens: resolved.maxTokens,
...defaultOpenAIChatExtra(client, model),
...extra,
};
if (client.stream) {
@@ -24,6 +24,7 @@ export const MINIMAX: InkosEndpoint = {
defaultTemperature: 0.9,
writingTemperature: 0.9,
models: [
{ id: "MiniMax-M3", maxOutput: 131072, contextWindowTokens: 1_000_000, enabled: true, releasedAt: "2026-07-01" },
{ id: "MiniMax-M2.7", maxOutput: 131072, contextWindowTokens: 204800, enabled: true, releasedAt: "2026-03-18" },
{ id: "MiniMax-M2.7-highspeed", maxOutput: 131072, contextWindowTokens: 204800, releasedAt: "2026-03-18" },
{ id: "MiniMax-M2.5", maxOutput: 131072, contextWindowTokens: 204800, releasedAt: "2026-02-12" },
+1 -1
View File
@@ -29,7 +29,7 @@ export const SERVICE_PRESETS: Record<string, ServicePreset> = {
temperatureRange: [0, 2],
defaultTemperature: 0.9,
writingTemperature: 0.9,
knownModels: ["MiniMax-M2.7", "MiniMax-M2.7-highspeed", "MiniMax-M2.5", "MiniMax-M2.5-highspeed", "MiniMax-M2.1", "MiniMax-M2.1-highspeed", "MiniMax-M2"],
knownModels: ["MiniMax-M3", "MiniMax-M2.7", "MiniMax-M2.7-highspeed", "MiniMax-M2.5", "MiniMax-M2.5-highspeed", "MiniMax-M2.1", "MiniMax-M2.1-highspeed", "MiniMax-M2"],
},
bailian: {
providerFamily: "anthropic",
+2 -2
View File
@@ -1,6 +1,6 @@
{
"name": "@actalk/inkos-studio",
"version": "1.6.2",
"version": "1.6.3",
"description": "InkOS Studio — web workbench for story creation, InkOS Short, covers, and open-world interactive play",
"type": "module",
"main": "dist/api/index.js",
@@ -31,7 +31,7 @@
"typecheck": "tsc --noEmit && tsc -p tsconfig.server.json --noEmit"
},
"dependencies": {
"@actalk/inkos-core": "workspace:*",
"@actalk/inkos-core": "1.6.3",
"@base-ui/react": "^1.3.0",
"@fontsource-variable/geist": "^5.2.8",
"@hono/node-server": "^1.13.0",
+3 -3
View File
@@ -15,10 +15,10 @@ importers:
packages/cli:
dependencies:
'@actalk/inkos-core':
specifier: workspace:*
specifier: 1.6.3
version: link:../core
'@actalk/inkos-studio':
specifier: workspace:*
specifier: 1.6.3
version: link:../studio
commander:
specifier: ^13.0.0
@@ -107,7 +107,7 @@ importers:
packages/studio:
dependencies:
'@actalk/inkos-core':
specifier: workspace:*
specifier: 1.6.3
version: link:../core
'@base-ui/react':
specifier: ^1.3.0
+8 -32
View File
@@ -1,15 +1,13 @@
/**
* Verify that source manifests are publishable once prepack normalization runs.
* Verify that source manifests are publishable as registry manifests.
*
* Usage:
* node scripts/verify-no-workspace-protocol.mjs packages/cli packages/core
* node ../../scripts/verify-no-workspace-protocol.mjs .
*
* This script is safe to run on source manifests before prepack.
*
* Checks two invariants before publish:
* 1. workspace:* / workspace:^ / workspace:~ references can be normalized to real versions
* 2. non-workspace internal dependencies already point at the current workspace version
* 1. dependencies / optionalDependencies / peerDependencies contain no workspace: specifiers
* 2. internal workspace dependencies point at the current workspace version
*/
import { access, readdir, readFile } from "node:fs/promises";
@@ -64,14 +62,6 @@ async function loadWorkspaceVersions(workspaceRoot) {
return versions;
}
function normalizeWorkspaceSpecifier(specifier, version) {
const value = specifier.slice("workspace:".length);
if (value === "*" || value === "") return version;
if (value === "^") return `^${version}`;
if (value === "~") return `~${version}`;
return value;
}
let failed = false;
const workspaceRoot = await findWorkspaceRoot(process.cwd());
const workspaceVersions = await loadWorkspaceVersions(workspaceRoot);
@@ -93,25 +83,11 @@ for (const dirArg of dirs) {
}
if (specifier.startsWith("workspace:")) {
if (!workspaceVersion) {
process.stderr.write(`FAIL: ${dir}${field}.${name}: ${specifier} (workspace package not found)\n`);
dirFailed = true;
failed = true;
continue;
}
const normalized = normalizeWorkspaceSpecifier(specifier, workspaceVersion);
if (
normalized !== workspaceVersion
&& normalized !== `^${workspaceVersion}`
&& normalized !== `~${workspaceVersion}`
) {
process.stderr.write(
`FAIL: ${dir}${field}.${name}: ${specifier} normalizes to ${normalized}, expected ${workspaceVersion}, ^${workspaceVersion}, or ~${workspaceVersion}\n`,
);
dirFailed = true;
failed = true;
}
process.stderr.write(
`FAIL: ${dir}${field}.${name}: ${specifier} (workspace protocol is not allowed in publish manifests)\n`,
);
dirFailed = true;
failed = true;
continue;
}