lili baedcd8153 fix(amodel-gen): reading 'w' 根因 footgun(host-config wiring)+ m3 timeout 300s
钻 reading 'w' 根因(minified 栈实锤):bootGameHost 读 i.viewport.w,而 buildTemplateHostConfig 返回的 viewport=undefined → M3 改 host-config 时破坏返回契约(漏 viewport 键/删 render.js export W/H)。非 minification、非产不出(两局当前 src 生产 min build 后 smoke PASS)。

更广模式:M3 游戏逻辑写得了(check+build 过),但反复搞坏插件 wiring——3 契约点:① host-config return shape(viewport)② registerOrder ③ game.js 取插件漏 .plugins 一层(createGame(opts.runtime) 后从 runtime 解构 → sceneFsm=undefined → reading 'define')。

本次改进:
- footgun ×2(prompt.mjs 红线 + littlejs skill ❌→✅):buildTemplateHostConfig 必返 5 键+viewport{w,h};取插件走两层 opts.runtime.plugins。
- m3.mjs timeout 180→300s:M3 大上下文 turn 触发 thinking 单 turn 实测 ~215s(裸简单调用仅 1-3s);180s 误超时+重试更拖。实测效果:wfix3 从 read-thrash(63读0build)转为真写码(27write/check+build PASS)、viewport bug 未再现。

注:踩了 prompt.mjs 模板字面量反引号坑 2 次(node --check 拦下,未浪费 run)——红线:prompt.mjs 内禁反引号。

未达绿:amodel e2e 仍 0/1——wiring 错类是 whack-a-mole(footgun 一个、M3 露下一个),且 per-attempt 步数预算下难一次收口。下一步战略(待创始人定):结构性把 host-config + 工厂 glue 设为固定 plumbing(agent 只写游戏逻辑),根除 wiring 错类。

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-22 07:54:13 -07:00

78 lines
3.5 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/**
* m3.mjs — new-api M3 客户端(openai 协议 + function-calling)
* owner:amodel-gen spike | 消费方:gen.mjs(ReAct 循环每轮调 chat)
*
* 单一职责:发一轮 chat/completions(带 tools)→ 返 {message, usage, finishReason}。
* 含超时 + 重试(429/5xx/IO 重试;4xx 不重试)。模型固定 MiniMax-M3(founder 定)。
* 密钥(§9):优先 env NEWAPI_KEY,否则从 docs/内网凭据与端点.md(内网允许入仓)解析——doc 是单一事实源。
*/
'use strict';
import { readFileSync } from 'node:fs';
import { join } from 'node:path';
import { REPO_ROOT } from './tools.mjs';
const BASE = (process.env.NEWAPI_BASE_URL || 'http://100.64.0.8:3000').replace(/\/+$/, '');
const MODEL = process.env.AMGEN_MODEL || 'MiniMax-M3'; // founder:只用 M3
const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
/** 取网关 token:env 优先,否则解析项目密钥文档(§9 单一事实源)。 */
function getKey() {
if (process.env.NEWAPI_KEY) return process.env.NEWAPI_KEY;
try {
const doc = readFileSync(join(REPO_ROOT, 'docs/内网凭据与端点.md'), 'utf8');
const m = doc.match(/NEWAPI_KEY[^\n]*?(sk-[A-Za-z0-9]+)/);
if (m) return m[1];
} catch { /* fall through */ }
throw new Error('找不到 NEWAPI_KEY(请设 env 或确认 docs/内网凭据与端点.md 存在)');
}
const KEY = getKey();
/**
* 发一轮带 tools 的 chat/completions。
* @param {Array<object>} messages openai 消息数组(含 system/user/assistant/tool)
* @param {Array<object>} tools function-calling 工具定义
* @param {{maxTokens?:number, temperature?:number, timeoutMs?:number}} [opt]
* @returns {Promise<{message:object, usage:object, finishReason:string}>}
*/
export async function chat(messages, tools, opt = {}) {
// timeout 300s:M3 在大上下文/代码生成 turn 会触发服务端 thinking、单 turn 实测达 ~215s(裸简单调用仅 1-3s);
// 180s 会误超时→重试(反而拖长:180s 等 + 再来一轮)。300s 让 thinking turn 一次完成、不浪费重试(HJ-AGI-003 实测)。
const { maxTokens = 16000, temperature = 0.3, timeoutMs = 300000 } = opt;
const body = { model: MODEL, messages, tools, tool_choice: 'auto', max_tokens: maxTokens, temperature };
let lastErr;
for (let attempt = 0; attempt < 3; attempt++) {
const ctrl = new AbortController();
const timer = setTimeout(() => ctrl.abort(), timeoutMs);
try {
const r = await fetch(`${BASE}/v1/chat/completions`, {
method: 'POST',
headers: { Authorization: `Bearer ${KEY}`, 'Content-Type': 'application/json' },
body: JSON.stringify(body),
signal: ctrl.signal,
});
clearTimeout(timer);
if (!r.ok) {
const txt = await r.text().catch(() => '');
// 4xx(除 429)= 请求本身错,不重试,直接抛
if (r.status !== 429 && r.status < 500) throw new Error(`HTTP ${r.status}: ${txt.slice(0, 300)}`);
lastErr = new Error(`HTTP ${r.status}: ${txt.slice(0, 200)}`);
await sleep(1500 * (attempt + 1));
continue;
}
const d = await r.json();
const ch = d.choices && d.choices[0];
if (!ch) throw new Error('响应无 choices: ' + JSON.stringify(d).slice(0, 300));
return { message: ch.message, usage: d.usage || {}, finishReason: ch.finish_reason };
} catch (e) {
clearTimeout(timer);
lastErr = e;
await sleep(1500 * (attempt + 1)); // 超时/IO 重试
}
}
throw lastErr || new Error('M3 chat 失败(重试耗尽)');
}
export { MODEL, BASE };