mirror of
https://github.com/diegosouzapw/OmniRoute.git
synced 2026-08-24 08:02:14 +03:00
Landed with the design call resolved per the owner's pick — **option 1**: the synced store is now endpoint-agnostic (persistDiscoveredModels and managedModelImport no longer drop non-chat models at write time), and chat selectability moved to read time (auto-pool expansion in autoStrategy applies filterChatSelectableModels; the models-route projection already had its chatOnly filter). Your discovery test now passes end-to-end (3/3): /api/show capabilities persist per connection and image/embedding requests route through the advertising host. Reconciliation notes: conflicted areas merged onto the current tip (adobe discovery import, requestedModel preflight signature, resolvedProvider fast-path coexists with the synced-route override — explicit resolution wins); carried base-red drains (#10055 memoization, #11071 test variants) dropped as already-landed; the managed-model-import exclusion test was propagated to the new contract (image/video models persist; the read filter still hides them from chat pickers — pinned by a new assertion). Full battery: 205/206 focused (the one red is a confirmed periodic-timer timing flake on the loaded devbox — 20/20 isolated), autoCombo vitest 30/30, combo suites 46/46, gates + typecheck clean. Thank you @yourspraveen — the capability probe + routing design was right; it just needed the store contract opened up. Fixes #11087.
43 lines
1.8 KiB
TypeScript
43 lines
1.8 KiB
TypeScript
type JsonRecord = Record<string, unknown>;
|
|
|
|
function asRecord(value: unknown): JsonRecord | null {
|
|
return value && typeof value === "object" && !Array.isArray(value) ? (value as JsonRecord) : null;
|
|
}
|
|
|
|
// ponytail: Kimi K3 (Moonshot) enforces a stricter tool-message contract than most
|
|
// OpenAI-compatible APIs: every role:"tool" message must carry a `name` field matching
|
|
// the function that issued the tool_call_id. When requests arrive through combo routing
|
|
// or format translation, the `name` field is frequently stripped, causing a 400.
|
|
// This builds a tool_call_id -> function.name lookup from assistant tool_calls and
|
|
// backfills missing names. Shared by KimiExecutor and DefaultExecutor (for BYOK providers).
|
|
// Upgrade path: if Moonshot relaxes this requirement, this function becomes a no-op.
|
|
export function ensureToolMessageNames(record: JsonRecord): JsonRecord {
|
|
if (!Array.isArray(record.messages)) return record;
|
|
|
|
const callIdToName = new Map<string, string>();
|
|
for (const msg of record.messages) {
|
|
const m = asRecord(msg);
|
|
if (!m || m.role !== "assistant" || !Array.isArray(m.tool_calls)) continue;
|
|
for (const tc of m.tool_calls as { id?: string; function?: { name?: string } }[]) {
|
|
if (tc?.id && typeof tc.function?.name === "string") {
|
|
callIdToName.set(String(tc.id), tc.function.name);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (callIdToName.size === 0) return record;
|
|
|
|
let modified = false;
|
|
const messages = record.messages.map((msg: unknown) => {
|
|
const m = asRecord(msg);
|
|
if (!m || m.role !== "tool" || typeof m.name === "string") return msg;
|
|
const callId = String(m.tool_call_id ?? "");
|
|
const resolvedName = callIdToName.get(callId);
|
|
if (!resolvedName) return msg;
|
|
modified = true;
|
|
return { ...m, name: resolvedName };
|
|
});
|
|
|
|
return modified ? { ...record, messages } : record;
|
|
}
|