@volter/twin-moonshot 0.1.0
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/LICENSE +202 -0
- package/README.md +164 -0
- package/dist/src/cli.d.ts +2 -0
- package/dist/src/cli.js +25 -0
- package/dist/src/index.d.ts +14 -0
- package/dist/src/index.js +86 -0
- package/dist/src/moonshot-budget.d.ts +57 -0
- package/dist/src/moonshot-budget.js +142 -0
- package/dist/src/moonshot-capabilities.d.ts +4 -0
- package/dist/src/moonshot-capabilities.js +1200 -0
- package/dist/src/moonshot-conformance.d.ts +14 -0
- package/dist/src/moonshot-conformance.js +405 -0
- package/dist/src/moonshot-connector.d.ts +168 -0
- package/dist/src/moonshot-connector.js +416 -0
- package/dist/src/moonshot-models.d.ts +36 -0
- package/dist/src/moonshot-models.js +37 -0
- package/dist/src/moonshot-scenario.d.ts +54 -0
- package/dist/src/moonshot-scenario.js +175 -0
- package/dist/src/moonshot-server.d.ts +13 -0
- package/dist/src/moonshot-server.js +202 -0
- package/dist/src/moonshot-stub.d.ts +70 -0
- package/dist/src/moonshot-stub.js +222 -0
- package/dist/src/moonshot-twin.d.ts +144 -0
- package/dist/src/moonshot-twin.js +1647 -0
- package/dist/src/moonshot-types.d.ts +251 -0
- package/dist/src/moonshot-types.js +19 -0
- package/package.json +53 -0
- package/src/cli.ts +25 -0
- package/src/index.ts +129 -0
- package/src/moonshot-budget.ts +163 -0
- package/src/moonshot-capabilities.ts +1220 -0
- package/src/moonshot-conformance.ts +416 -0
- package/src/moonshot-connector.ts +465 -0
- package/src/moonshot-models.ts +89 -0
- package/src/moonshot-scenario.ts +194 -0
- package/src/moonshot-server.ts +220 -0
- package/src/moonshot-stub.ts +230 -0
- package/src/moonshot-twin.ts +1670 -0
- package/src/moonshot-types.ts +225 -0
|
@@ -0,0 +1,1220 @@
|
|
|
1
|
+
// Moonshot capability manifest — the EXPECTED REAL-PRODUCT SURFACE (the target), authored
|
|
2
|
+
// top-down from what the Moonshot Platform API actually does — NOT from what this twin has
|
|
3
|
+
// built. The denominator was enumerated from FIRST-PARTY sources, all read 2026-09-16:
|
|
4
|
+
// • Moonshot's own OpenAPI 3.1.0 document (https://platform.kimi.ai/docs/openapi.json —
|
|
5
|
+
// 19 operations / 69 schemas): every request schema's parameter union, per-request model
|
|
6
|
+
// enum, required fields and documented 400s. This is what fixed the three inference
|
|
7
|
+
// protocols (OpenAI-compatible /v1/chat/completions + /v1/responses, Anthropic-compatible
|
|
8
|
+
// /anthropic/v1/messages), the closed thinking/reasoning_effort sets, the batch
|
|
9
|
+
// completion_window grammar, the files purposes, and the balance envelope;
|
|
10
|
+
// • platform.kimi.ai/docs/{overview,api-reference,errors,models,pricing/limits,
|
|
11
|
+
// guides/context-caching}: the error-type table, the tier limits (RPM 3 at Tier 0), the
|
|
12
|
+
// model table (context windows, retired ids), and the context-caching semantics behind
|
|
13
|
+
// the usage split.
|
|
14
|
+
// Most entries start as `todo` and coverage reads LOW until the twin truly reaches 100% of
|
|
15
|
+
// the API. `verify()` (required to count as done) is ground truth; `expected:'done'` only on
|
|
16
|
+
// capabilities we genuinely claim, so a broken one shows as a regression.
|
|
17
|
+
//
|
|
18
|
+
// There are NO carve-outs. A twin is a deterministic, offline model of the vendor's API
|
|
19
|
+
// contract: where the vendor runs a model, the twin returns a DETERMINISTIC labeled stub, and
|
|
20
|
+
// that stub IS the twin's answer, not a shortfall from a "real" one. The protocol envelope
|
|
21
|
+
// (shape/streaming/tool_calls/usage/reasoning) is faithful. Every entry here is either done
|
|
22
|
+
// or todo.
|
|
23
|
+
//
|
|
24
|
+
// TIERING (§6 rule 3, per the groq pack's corrected reading): `core` = "first-week-of-every-
|
|
25
|
+
// integration". For Moonshot that is the chat-completions + streaming + tools + errors + auth
|
|
26
|
+
// spine plus the conformance and connector-pull backbone. The Responses and Messages surfaces
|
|
27
|
+
// (kimi-k3 only, newer), files/batches (batch workloads), the token/signature/web-search
|
|
28
|
+
// helpers, and balance are `common`. Single-field refinements are `niche`.
|
|
29
|
+
//
|
|
30
|
+
// (Moonshot is an API-first vendor — platform.moonshot.cn's console is a keys/usage console,
|
|
31
|
+
// not where the work happens — so this pack ships NO mirror and has NO UI capabilities.)
|
|
32
|
+
import { mkdtempSync, rmSync } from 'node:fs';
|
|
33
|
+
import { tmpdir } from 'node:os';
|
|
34
|
+
import { join } from 'node:path';
|
|
35
|
+
import { checkCapabilities, type CapabilityReport, type CapabilitySpec, verifyBoundary, isInfrastructureError, harnessError } from '@volter/world-tooling';
|
|
36
|
+
import { pendingActions, projectResources } from '@volter/world-core';
|
|
37
|
+
import { handleMoonshotTwinRequest, messagesSignature, MESSAGES_PREFIX, MOONSHOT_API_PREFIX, type MoonshotResponseEnvelope } from './moonshot-twin.ts';
|
|
38
|
+
import {
|
|
39
|
+
fullSyncMoonshot,
|
|
40
|
+
moonshotRequestForAction,
|
|
41
|
+
pullMoonshotState,
|
|
42
|
+
pushMoonshotAction,
|
|
43
|
+
pushPendingMoonshotActions,
|
|
44
|
+
syncMoonshotFromReal,
|
|
45
|
+
type MoonshotExecute,
|
|
46
|
+
} from './moonshot-connector.ts';
|
|
47
|
+
import { MoonshotBudget, MoonshotBudgetError, MOONSHOT_BUDGET_CEILING, MOONSHOT_CALL_WEIGHTS, MOONSHOT_RATE_BUDGET_WINDOW_MS, moonshotBudgetPath, moonshotCallWeight } from './moonshot-budget.ts';
|
|
48
|
+
import type { MessagesSseEvent, SseEvent } from './moonshot-types.ts';
|
|
49
|
+
|
|
50
|
+
// ── API verify: drive REAL requests against a fresh temp root, then assert status/shape ──
|
|
51
|
+
type Step = { m: string; p: string; b?: unknown };
|
|
52
|
+
type Body = Record<string, any>;
|
|
53
|
+
|
|
54
|
+
/** Run a sequence of real Moonshot requests against an isolated root; return all responses. */
|
|
55
|
+
async function withRoot(steps: (h: (s: Step) => Promise<MoonshotResponseEnvelope>, root: string) => Promise<boolean>): Promise<boolean> {
|
|
56
|
+
const root = mkdtempSync(join(tmpdir(), 'moonshot-cap-'));
|
|
57
|
+
const h = (s: Step) => handleMoonshotTwinRequest({ method: s.m, path: s.p, body: s.b === undefined ? undefined : JSON.stringify(s.b), root });
|
|
58
|
+
try {
|
|
59
|
+
// `root` is handed to the steps too, so a verify can inspect the LOG (projectResources /
|
|
60
|
+
// pendingActions) and not merely the responses — the difference between proving "the reply
|
|
61
|
+
// did not change" and proving "nothing was written" (§9 round two).
|
|
62
|
+
return await verifyBoundary('moonshot.withRoot', () => steps(h, root));
|
|
63
|
+
} finally {
|
|
64
|
+
rmSync(root, { recursive: true, force: true });
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/** Like withRoot, but the request helper passes request HEADERS through (for auth, the
|
|
69
|
+
* deterministic 429/503 triggers, and the signature headers). */
|
|
70
|
+
type StepH = Step & { headers?: Record<string, string> };
|
|
71
|
+
async function withRootH(steps: (h: (s: StepH) => Promise<MoonshotResponseEnvelope>) => Promise<boolean>): Promise<boolean> {
|
|
72
|
+
const root = mkdtempSync(join(tmpdir(), 'moonshot-cap-'));
|
|
73
|
+
const h = (s: StepH) => handleMoonshotTwinRequest({ method: s.m, path: s.p, body: s.b === undefined ? undefined : JSON.stringify(s.b), root, ...(s.headers ? { headers: s.headers } : {}) });
|
|
74
|
+
try {
|
|
75
|
+
return await verifyBoundary('moonshot.withRootH', () => steps(h));
|
|
76
|
+
} finally {
|
|
77
|
+
rmSync(root, { recursive: true, force: true });
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/** Collect the streaming SSE events for a chat request against an isolated root. */
|
|
82
|
+
function withStream(body: unknown, fn: (events: SseEvent[], final: MoonshotResponseEnvelope) => boolean): Promise<boolean> {
|
|
83
|
+
return new Promise<boolean>((resolve, reject) => {
|
|
84
|
+
const root = mkdtempSync(join(tmpdir(), 'moonshot-cap-'));
|
|
85
|
+
const events: SseEvent[] = [];
|
|
86
|
+
handleMoonshotTwinRequest({ method: 'POST', path: `${MOONSHOT_API_PREFIX}/chat/completions`, body: JSON.stringify({ ...(body as Body), stream: true }), root, sseSink: (e) => events.push(e) })
|
|
87
|
+
.then((final) => resolve(fn(events, final)))
|
|
88
|
+
.catch((err) => { if (isInfrastructureError(err)) reject(harnessError('moonshot.withStream', err)); else resolve(false); })
|
|
89
|
+
.finally(() => rmSync(root, { recursive: true, force: true }));
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/** Collect the named-event SSE frames (Messages / Responses grammar) for a request. */
|
|
94
|
+
function withNamedStream(path: string, body: unknown, fn: (events: MessagesSseEvent[], final: MoonshotResponseEnvelope) => boolean): Promise<boolean> {
|
|
95
|
+
return new Promise<boolean>((resolve, reject) => {
|
|
96
|
+
const root = mkdtempSync(join(tmpdir(), 'moonshot-cap-'));
|
|
97
|
+
const events: MessagesSseEvent[] = [];
|
|
98
|
+
handleMoonshotTwinRequest({ method: 'POST', path, body: JSON.stringify({ ...(body as Body), stream: true }), root, messagesSseSink: (e) => events.push(e) })
|
|
99
|
+
.then((final) => resolve(fn(events, final)))
|
|
100
|
+
.catch((err) => { if (isInfrastructureError(err)) reject(harnessError('moonshot.withNamedStream', err)); else resolve(false); })
|
|
101
|
+
.finally(() => rmSync(root, { recursive: true, force: true }));
|
|
102
|
+
});
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/** A connector verify against an isolated root, with the injected fake executor the test builds. */
|
|
106
|
+
async function withConnectorRoot(id: string, fn: (root: string) => Promise<boolean>): Promise<boolean> {
|
|
107
|
+
const root = mkdtempSync(join(tmpdir(), 'moonshot-cap-'));
|
|
108
|
+
try {
|
|
109
|
+
return await fn(root);
|
|
110
|
+
} catch (err) {
|
|
111
|
+
if (isInfrastructureError(err)) throw harnessError(id, err);
|
|
112
|
+
return false;
|
|
113
|
+
} finally {
|
|
114
|
+
rmSync(root, { recursive: true, force: true });
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
const ok = (r: MoonshotResponseEnvelope) => r.status >= 200 && r.status < 300;
|
|
119
|
+
const id = (r: MoonshotResponseEnvelope) => (r.body as Body)?.id as string;
|
|
120
|
+
const errType = (r: MoonshotResponseEnvelope) => (r.body as Body)?.error?.type as string;
|
|
121
|
+
|
|
122
|
+
// ── shorthands (mirror the groq manifest) ──
|
|
123
|
+
const done = (id: string, area: string, title: string, dimension: CapabilitySpec['dimension'], tier: CapabilitySpec['tier'], verify: CapabilitySpec['verify']): CapabilitySpec => ({ id, area, title, dimension, tier, expected: 'done', verify });
|
|
124
|
+
const todo = (id: string, area: string, title: string, dimension: CapabilitySpec['dimension'], tier: CapabilitySpec['tier']): CapabilitySpec => ({ id, area, title, dimension, tier, expected: 'todo' });
|
|
125
|
+
|
|
126
|
+
const CHAT_PATH = `${MOONSHOT_API_PREFIX}/chat/completions`;
|
|
127
|
+
const MESSAGES_PATH = `${MESSAGES_PREFIX}/messages`;
|
|
128
|
+
const CHAT = (extra: Record<string, unknown> = {}) => ({ model: 'kimi-k3', messages: [{ role: 'user', content: 'hello twin' }], ...extra });
|
|
129
|
+
const K26 = (extra: Record<string, unknown> = {}) => ({ model: 'kimi-k2.6', messages: [{ role: 'user', content: 'hello twin' }], ...extra });
|
|
130
|
+
const WEATHER_TOOL = { type: 'function', function: { name: 'get_weather', parameters: { type: 'object', properties: { city: { type: 'string' }, days: { type: 'integer' } } } } };
|
|
131
|
+
|
|
132
|
+
/** Seed a batch-purpose file and a batch over it. Returns the batch id (or null). */
|
|
133
|
+
async function seedBatch(h: (s: Step) => Promise<MoonshotResponseEnvelope>): Promise<string | null> {
|
|
134
|
+
const f = await h({ m: 'POST', p: `${MOONSHOT_API_PREFIX}/files`, b: { purpose: 'batch', filename: 'in.jsonl', content: '{"custom_id":"a"}' } });
|
|
135
|
+
if (!ok(f)) return null;
|
|
136
|
+
const b = await h({ m: 'POST', p: `${MOONSHOT_API_PREFIX}/batches`, b: { input_file_id: id(f), endpoint: '/v1/chat/completions', completion_window: '24h' } });
|
|
137
|
+
return ok(b) ? id(b) : null;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/** A recording fake executor: `calls` is the ground truth a connector verify reads. */
|
|
141
|
+
function fakeExecute(reply: (method: string, path: string) => unknown = () => ({ data: [] })): { execute: MoonshotExecute; calls: string[]; bodies: Array<Record<string, unknown> | undefined> } {
|
|
142
|
+
const calls: string[] = [];
|
|
143
|
+
// The BODY is recorded too: a push verify that asserts only method+path cannot see a payload
|
|
144
|
+
// that would 400 at the real vendor (§9 round one, groq finding 6).
|
|
145
|
+
const bodies: Array<Record<string, unknown> | undefined> = [];
|
|
146
|
+
const execute: MoonshotExecute = async (method, path, body) => {
|
|
147
|
+
calls.push(`${method} ${path}`);
|
|
148
|
+
bodies.push(body);
|
|
149
|
+
return reply(method, path) as Awaited<ReturnType<MoonshotExecute>>;
|
|
150
|
+
};
|
|
151
|
+
return { execute, calls, bodies };
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
export const MOONSHOT_CAPABILITIES: CapabilitySpec[] = [
|
|
155
|
+
// ── Planned gaps ──────────────────────────────────────────────────────────────────────
|
|
156
|
+
todo('moonshot.chat.per_request_response_ids', 'chat', 'Chat ids are unique per request: `id` is derived from a hash of (messages, model) today, so two identical calls share one id', 'api', 'niche'),
|
|
157
|
+
todo('moonshot.batches.cancellation_settles', 'batches', 'Batch cancel eventually settles cancelling→cancelled asynchronously (the twin stops at `cancelling`; settling on a READ would break the read-only contract)', 'api', 'niche'),
|
|
158
|
+
todo('moonshot.connector.push_file_create', 'connector', 'Connector: push a local file create (real POST /v1/files is multipart with the file body; the JSON executor cannot express it)', 'connector', 'niche'),
|
|
159
|
+
todo('moonshot.connector.unpushable_actions_drain', 'connector', 'Connector: a permanently-unpushable action can be acknowledged so `pendingActions` can reach empty again', 'connector', 'niche'),
|
|
160
|
+
|
|
161
|
+
// ── Chat Completions (the protocol envelope — faithful) ────────────────────────────────
|
|
162
|
+
done('moonshot.chat.create', 'chat', 'Chat: create → faithful envelope (id/object/created/model/choices/usage with cached_tokens)', 'api', 'core', () =>
|
|
163
|
+
withRoot(async (h) => {
|
|
164
|
+
const r = await h({ m: 'POST', p: CHAT_PATH, b: CHAT() });
|
|
165
|
+
if (!ok(r)) return false;
|
|
166
|
+
const b = r.body as Body;
|
|
167
|
+
if (b.object !== 'chat.completion' || b.model !== 'kimi-k3' || typeof b.created !== 'number') return false;
|
|
168
|
+
if (!String(b.id).startsWith('chatcmpl-twin-')) return false;
|
|
169
|
+
const c = b.choices?.[0];
|
|
170
|
+
if (!c || c.message?.role !== 'assistant' || typeof c.message?.content !== 'string' || c.finish_reason !== 'stop') return false;
|
|
171
|
+
const u = b.usage;
|
|
172
|
+
return typeof u?.prompt_tokens === 'number' && u.prompt_tokens > 0
|
|
173
|
+
&& u.total_tokens === u.prompt_tokens + u.completion_tokens
|
|
174
|
+
&& typeof u.cached_tokens === 'number';
|
|
175
|
+
}),
|
|
176
|
+
),
|
|
177
|
+
done('moonshot.chat.stub_labeled', 'chat', 'Stub completion is clearly labeled as a twin stub (not real output)', 'api', 'core', () =>
|
|
178
|
+
withRoot(async (h) => {
|
|
179
|
+
const r = await h({ m: 'POST', p: CHAT_PATH, b: CHAT() });
|
|
180
|
+
const text = (r.body as Body).choices?.[0]?.message?.content as string;
|
|
181
|
+
return ok(r) && typeof text === 'string' && text.includes('[twin-stub:kimi-k3]') && text.includes('hello twin');
|
|
182
|
+
}),
|
|
183
|
+
),
|
|
184
|
+
done('moonshot.chat.validation', 'chat', 'Chat validation (model required + known, messages required + non-empty, roles closed set)', 'api', 'core', () =>
|
|
185
|
+
withRoot(async (h) => {
|
|
186
|
+
const noModel = await h({ m: 'POST', p: CHAT_PATH, b: { messages: [{ role: 'user', content: 'x' }] } });
|
|
187
|
+
const unknown = await h({ m: 'POST', p: CHAT_PATH, b: CHAT({ model: 'moonshot-v1-8k' }) });
|
|
188
|
+
const noMsg = await h({ m: 'POST', p: CHAT_PATH, b: { model: 'kimi-k3' } });
|
|
189
|
+
const empty = await h({ m: 'POST', p: CHAT_PATH, b: CHAT({ messages: [] }) });
|
|
190
|
+
const badRole = await h({ m: 'POST', p: CHAT_PATH, b: CHAT({ messages: [{ role: 'chef', content: 'x' }] }) });
|
|
191
|
+
return [noModel, unknown, noMsg, empty, badRole].every((r) => r.status === 400 && errType(r) === 'invalid_request_error')
|
|
192
|
+
&& String((unknown.body as Body).error.message).includes('moonshot-v1-8k');
|
|
193
|
+
}),
|
|
194
|
+
),
|
|
195
|
+
done('moonshot.chat.deterministic', 'chat', 'Chat: an identical request replays byte-identically (serve-path determinism)', 'api', 'core', () =>
|
|
196
|
+
withRoot(async (h) => {
|
|
197
|
+
const a = await h({ m: 'POST', p: CHAT_PATH, b: CHAT() });
|
|
198
|
+
const b = await h({ m: 'POST', p: CHAT_PATH, b: CHAT() });
|
|
199
|
+
if (!ok(a) || !ok(b)) return false;
|
|
200
|
+
const ba = a.body as Body;
|
|
201
|
+
// A real, populated envelope — not merely "equal because both are undefined".
|
|
202
|
+
if (typeof ba.id !== 'string' || !ba.id.startsWith('chatcmpl-twin-')) return false;
|
|
203
|
+
if (typeof ba.usage?.total_tokens !== 'number') return false;
|
|
204
|
+
return JSON.stringify(a.body) === JSON.stringify(b.body);
|
|
205
|
+
}),
|
|
206
|
+
),
|
|
207
|
+
done('moonshot.chat.max_completion_tokens', 'chat', 'Chat: max_completion_tokens (and the max_tokens alias) cap the OUTPUT — finish_reason length AND usage.completion_tokens counts what was emitted; 0 is a 400', 'api', 'common', () =>
|
|
208
|
+
withRoot(async (h) => {
|
|
209
|
+
const long = [{ role: 'user', content: 'please produce a long answer that exceeds one token' }];
|
|
210
|
+
const a = await h({ m: 'POST', p: CHAT_PATH, b: CHAT({ max_completion_tokens: 1, messages: long }) });
|
|
211
|
+
const b = await h({ m: 'POST', p: CHAT_PATH, b: CHAT({ max_tokens: 1, messages: long }) });
|
|
212
|
+
const bad = await h({ m: 'POST', p: CHAT_PATH, b: CHAT({ max_completion_tokens: 0 }) });
|
|
213
|
+
if (!ok(a) || !ok(b) || bad.status !== 400) return false;
|
|
214
|
+
for (const r of [a, b]) {
|
|
215
|
+
const body = r.body as Body;
|
|
216
|
+
if (body.choices[0].finish_reason !== 'length') return false;
|
|
217
|
+
// THE CAP IS APPLIED, not merely reported: the emitted text fits the cap and
|
|
218
|
+
// completion_tokens counts the emitted content (+ reasoning) — usage that counts
|
|
219
|
+
// uncapped content while claiming the cap was the round-two finding.
|
|
220
|
+
const msg = body.choices[0].message;
|
|
221
|
+
const emitted = String(msg.content ?? '').length + String(msg.reasoning_content ?? '').length;
|
|
222
|
+
if (body.usage.completion_tokens * 4 < emitted) return false;
|
|
223
|
+
}
|
|
224
|
+
return true;
|
|
225
|
+
}),
|
|
226
|
+
),
|
|
227
|
+
done('moonshot.chat.context_window_overflow', 'chat', 'Chat: input + max_completion_tokens beyond the model context window is a 400 invalid_request_error', 'api', 'common', () =>
|
|
228
|
+
withRoot(async (h) => {
|
|
229
|
+
// kimi-k2.6's window is 262,144; a max_completion_tokens over it must refuse.
|
|
230
|
+
const over = await h({ m: 'POST', p: CHAT_PATH, b: K26({ max_completion_tokens: 300_000 }) });
|
|
231
|
+
const fits = await h({ m: 'POST', p: CHAT_PATH, b: K26({ max_completion_tokens: 1000 }) });
|
|
232
|
+
return over.status === 400 && errType(over) === 'invalid_request_error'
|
|
233
|
+
&& String((over.body as Body).error.message).includes('context window') && ok(fits);
|
|
234
|
+
}),
|
|
235
|
+
),
|
|
236
|
+
done('moonshot.chat.stop', 'chat', "Chat: stop truncates at the EARLIEST hit; the closed limits (≤5 entries, ≤32 bytes each) are enforced", 'api', 'common', () =>
|
|
237
|
+
withRoot(async (h) => {
|
|
238
|
+
const one = await h({ m: 'POST', p: CHAT_PATH, b: CHAT({ stop: 'Echoing' }) });
|
|
239
|
+
const many = await h({ m: 'POST', p: CHAT_PATH, b: CHAT({ stop: ['Echoing', 'deterministic'] }) });
|
|
240
|
+
const tooMany = await h({ m: 'POST', p: CHAT_PATH, b: CHAT({ stop: ['a', 'b', 'c', 'd', 'e', 'f'] }) });
|
|
241
|
+
const tooLong = await h({ m: 'POST', p: CHAT_PATH, b: CHAT({ stop: 'x'.repeat(33) }) });
|
|
242
|
+
const t1 = (one.body as Body).choices[0].message.content as string;
|
|
243
|
+
const t2 = (many.body as Body).choices[0].message.content as string;
|
|
244
|
+
return ok(one) && ok(many) && tooMany.status === 400 && tooLong.status === 400
|
|
245
|
+
&& !t1.includes('Echoing') && !t2.includes('deterministic') && !t2.includes('Echoing') && t2.length < t1.length;
|
|
246
|
+
}),
|
|
247
|
+
),
|
|
248
|
+
done('moonshot.chat.multi_turn', 'chat', 'Chat: multi-turn user/assistant history accepted and echoed from the last user turn', 'api', 'common', () =>
|
|
249
|
+
withRoot(async (h) => {
|
|
250
|
+
const r = await h({ m: 'POST', p: CHAT_PATH, b: CHAT({ messages: [{ role: 'user', content: 'first question' }, { role: 'assistant', content: 'reply' }, { role: 'user', content: 'second question' }] }) });
|
|
251
|
+
const text = (r.body as Body).choices?.[0]?.message?.content as string;
|
|
252
|
+
return ok(r) && text.includes('second question') && !text.includes('first question');
|
|
253
|
+
}),
|
|
254
|
+
),
|
|
255
|
+
done('moonshot.chat.system_message', 'chat', 'Chat: system messages count toward prompt_tokens', 'api', 'common', () =>
|
|
256
|
+
withRoot(async (h) => {
|
|
257
|
+
const without = await h({ m: 'POST', p: CHAT_PATH, b: CHAT() });
|
|
258
|
+
const withSys = await h({ m: 'POST', p: CHAT_PATH, b: CHAT({ messages: [{ role: 'system', content: 'You are a careful, verbose assistant.' }, { role: 'user', content: 'hello twin' }] }) });
|
|
259
|
+
return ok(without) && ok(withSys) && (withSys.body as Body).usage.prompt_tokens > (without.body as Body).usage.prompt_tokens;
|
|
260
|
+
}),
|
|
261
|
+
),
|
|
262
|
+
done('moonshot.chat.vision_gating', 'chat', 'Chat: image_url parts accepted on the vision models (kimi-k3, kimi-k2.6) and refused on kimi-k2.7-code', 'api', 'common', () =>
|
|
263
|
+
withRoot(async (h) => {
|
|
264
|
+
const img = [{ role: 'user', content: [{ type: 'text', text: 'what is this' }, { type: 'image_url', image_url: { url: 'https://example.test/cat.png' } }] }];
|
|
265
|
+
const k3 = await h({ m: 'POST', p: CHAT_PATH, b: CHAT({ messages: img }) });
|
|
266
|
+
const k26 = await h({ m: 'POST', p: CHAT_PATH, b: K26({ messages: img }) });
|
|
267
|
+
const k27 = await h({ m: 'POST', p: CHAT_PATH, b: { model: 'kimi-k2.7-code', messages: img } });
|
|
268
|
+
return ok(k3) && ok(k26) && k27.status === 400
|
|
269
|
+
&& String((k27.body as Body).error.message).includes('does not support image input');
|
|
270
|
+
}),
|
|
271
|
+
),
|
|
272
|
+
done('moonshot.chat.dynamic_tool_message', 'chat', "Chat: kimi-k3's dynamic tool loading — a system message with `tools` and no content — is accepted; the same shape on any other role is a 400", 'api', 'niche', () =>
|
|
273
|
+
withRoot(async (h) => {
|
|
274
|
+
const good = await h({ m: 'POST', p: CHAT_PATH, b: { model: 'kimi-k3', messages: [{ role: 'system', tools: [WEATHER_TOOL] }, { role: 'user', content: 'hi' }] } });
|
|
275
|
+
const bad = await h({ m: 'POST', p: CHAT_PATH, b: { model: 'kimi-k3', messages: [{ role: 'user', tools: [WEATHER_TOOL] }, { role: 'user', content: 'hi' }] } });
|
|
276
|
+
return ok(good) && bad.status === 400
|
|
277
|
+
&& String((bad.body as Body).error.message).includes("must use the 'system' role");
|
|
278
|
+
}),
|
|
279
|
+
),
|
|
280
|
+
done('moonshot.chat.logprobs', 'chat', "Chat: logprobs/top_logprobs are real Moonshot surface — accepted (0..20), but top_logprobs without logprobs:true is a 400", 'api', 'niche', () =>
|
|
281
|
+
withRoot(async (h) => {
|
|
282
|
+
// Moonshot's OpenAPI DECLARES logprobs + top_logprobs (0..20) on chat — unlike Groq,
|
|
283
|
+
// which 400s them. The documented coupling: "logprobs must be set to true when
|
|
284
|
+
// top_logprobs is used".
|
|
285
|
+
const okLp = await h({ m: 'POST', p: CHAT_PATH, b: CHAT({ logprobs: true, top_logprobs: 5 }) });
|
|
286
|
+
const orphan = await h({ m: 'POST', p: CHAT_PATH, b: CHAT({ top_logprobs: 5 }) });
|
|
287
|
+
const range = await h({ m: 'POST', p: CHAT_PATH, b: CHAT({ logprobs: true, top_logprobs: 21 }) });
|
|
288
|
+
return ok(okLp) && orphan.status === 400 && range.status === 400
|
|
289
|
+
&& String((orphan.body as Body).error.message).includes("'logprobs' must be set to true");
|
|
290
|
+
}),
|
|
291
|
+
),
|
|
292
|
+
done('moonshot.chat.prompt_cache_key', 'chat', "Chat: prompt_cache_key is LOAD-BEARING (context caching) — with a key and a >256-token prompt the usage carries a cached_tokens split; without one the cache is cold (0)", 'api', 'niche', () =>
|
|
293
|
+
withRoot(async (h) => {
|
|
294
|
+
// Moonshot's context caching is keyed by prompt_cache_key: a request WITHOUT one has no
|
|
295
|
+
// cache namespace to hit, so cached_tokens stays 0 however long the prompt is. The long
|
|
296
|
+
// prompt (>256 tokens) crosses the documented threshold, so the split's presence/absence
|
|
297
|
+
// is attributable to the key alone — a parse-only acceptance (deleting it reddened
|
|
298
|
+
// nothing) was the round-two finding.
|
|
299
|
+
const long = 'please cache this prompt '.repeat(120);
|
|
300
|
+
const keyed = await h({ m: 'POST', p: CHAT_PATH, b: CHAT({ prompt_cache_key: 'cache-1', messages: [{ role: 'user', content: long }] }) });
|
|
301
|
+
const keyless = await h({ m: 'POST', p: CHAT_PATH, b: CHAT({ messages: [{ role: 'user', content: long }] }) });
|
|
302
|
+
const kb = keyed.body as Body;
|
|
303
|
+
const lb = keyless.body as Body;
|
|
304
|
+
return ok(keyed) && ok(keyless)
|
|
305
|
+
&& typeof kb.usage?.cached_tokens === 'number' && kb.usage.cached_tokens > 0
|
|
306
|
+
&& lb.usage?.cached_tokens === 0;
|
|
307
|
+
}),
|
|
308
|
+
),
|
|
309
|
+
done('moonshot.chat.reasoning_effort', 'chat', "Chat: reasoning_effort is kimi-k3-only with the closed set ('low'|'high'|'max') — any other model is a 400", 'api', 'common', () =>
|
|
310
|
+
withRoot(async (h) => {
|
|
311
|
+
const low = await h({ m: 'POST', p: CHAT_PATH, b: CHAT({ reasoning_effort: 'low' }) });
|
|
312
|
+
const badVal = await h({ m: 'POST', p: CHAT_PATH, b: CHAT({ reasoning_effort: 'medium' }) });
|
|
313
|
+
const wrongModel = await h({ m: 'POST', p: CHAT_PATH, b: K26({ reasoning_effort: 'low' }) });
|
|
314
|
+
return ok(low) && badVal.status === 400 && wrongModel.status === 400
|
|
315
|
+
&& String((wrongModel.body as Body).error.message).includes('not a parameter of kimi-k2.6');
|
|
316
|
+
}),
|
|
317
|
+
),
|
|
318
|
+
done('moonshot.chat.thinking', 'chat', "Chat: the thinking object is per-model — kimi-k2.6 accepts {type:'enabled'|'disabled', keep:'all'|null}; kimi-k2.7-code rejects type 'disabled'; kimi-k3 has no thinking parameter", 'api', 'common', () =>
|
|
319
|
+
withRoot(async (h) => {
|
|
320
|
+
const k26off = await h({ m: 'POST', p: CHAT_PATH, b: K26({ thinking: { type: 'disabled' } }) });
|
|
321
|
+
const k26on = await h({ m: 'POST', p: CHAT_PATH, b: K26({ thinking: { type: 'enabled', keep: 'all' } }) });
|
|
322
|
+
const k27off = await h({ m: 'POST', p: CHAT_PATH, b: { model: 'kimi-k2.7-code', messages: [{ role: 'user', content: 'x' }], thinking: { type: 'disabled' } } });
|
|
323
|
+
const k3 = await h({ m: 'POST', p: CHAT_PATH, b: CHAT({ thinking: { type: 'enabled' } }) });
|
|
324
|
+
return ok(k26off) && ok(k26on) && k27off.status === 400 && k3.status === 400;
|
|
325
|
+
}),
|
|
326
|
+
),
|
|
327
|
+
done('moonshot.chat.reasoning_content', 'chat', 'Chat: thinking mode surfaces reasoning_content on the assistant message; thinking disabled does not', 'api', 'common', () =>
|
|
328
|
+
withRoot(async (h) => {
|
|
329
|
+
const k3 = await h({ m: 'POST', p: CHAT_PATH, b: CHAT() });
|
|
330
|
+
const k3Msg = (k3.body as Body).choices[0].message;
|
|
331
|
+
const off = await h({ m: 'POST', p: CHAT_PATH, b: K26({ thinking: { type: 'disabled' } }) });
|
|
332
|
+
const offMsg = (off.body as Body).choices[0].message;
|
|
333
|
+
return ok(k3) && ok(off)
|
|
334
|
+
&& typeof k3Msg.reasoning_content === 'string' && k3Msg.reasoning_content.includes('[twin-stub:kimi-k3]')
|
|
335
|
+
&& !('reasoning_content' in offMsg);
|
|
336
|
+
}),
|
|
337
|
+
),
|
|
338
|
+
done('moonshot.chat.tool_calls', 'chat', 'Chat: tool_calls envelope (function name + schema-shaped JSON arguments), tool_choice none/named honored', 'api', 'core', () =>
|
|
339
|
+
withRoot(async (h) => {
|
|
340
|
+
const r = await h({ m: 'POST', p: CHAT_PATH, b: CHAT({ tools: [WEATHER_TOOL] }) });
|
|
341
|
+
const c = (r.body as Body).choices[0];
|
|
342
|
+
if (!ok(r) || c.finish_reason !== 'tool_calls') return false;
|
|
343
|
+
const tc = c.message.tool_calls?.[0];
|
|
344
|
+
if (tc?.type !== 'function' || tc.function?.name !== 'get_weather') return false;
|
|
345
|
+
const args = JSON.parse(tc.function.arguments);
|
|
346
|
+
if (!args || typeof args !== 'object') return false;
|
|
347
|
+
const none = await h({ m: 'POST', p: CHAT_PATH, b: CHAT({ tools: [WEATHER_TOOL], tool_choice: 'none' }) });
|
|
348
|
+
const named = await h({ m: 'POST', p: CHAT_PATH, b: CHAT({ tools: [WEATHER_TOOL], tool_choice: { type: 'function', function: { name: 'get_weather' } } }) });
|
|
349
|
+
const namedTc = (named.body as Body).choices[0].message.tool_calls?.[0];
|
|
350
|
+
return none.status === 200 && (none.body as Body).choices[0].finish_reason === 'stop'
|
|
351
|
+
&& ok(named) && namedTc?.function?.name === 'get_weather';
|
|
352
|
+
}),
|
|
353
|
+
),
|
|
354
|
+
done('moonshot.chat.tool_name_validation', 'chat', "Chat: tool function names must match the documented regex ^[a-zA-Z_][a-zA-Z0-9-_]{0,127}$ (the leading-digit boundary is pinned; the 128-char boundary is enforced by the same regex)", 'api', 'niche', () =>
|
|
355
|
+
withRoot(async (h) => {
|
|
356
|
+
const bad = await h({ m: 'POST', p: CHAT_PATH, b: CHAT({ tools: [{ type: 'function', function: { name: '9bad name', parameters: {} } }] }) });
|
|
357
|
+
const good = await h({ m: 'POST', p: CHAT_PATH, b: CHAT({ tools: [WEATHER_TOOL] }) });
|
|
358
|
+
return bad.status === 400 && ok(good);
|
|
359
|
+
}),
|
|
360
|
+
),
|
|
361
|
+
done('moonshot.chat.structured_outputs', 'chat', "Chat: response_format json_object / json_schema (name + schema required) yield JSON stub content; an unknown type is a 400", 'api', 'common', () =>
|
|
362
|
+
withRoot(async (h) => {
|
|
363
|
+
const jo = await h({ m: 'POST', p: CHAT_PATH, b: CHAT({ response_format: { type: 'json_object' } }) });
|
|
364
|
+
const js = await h({ m: 'POST', p: CHAT_PATH, b: CHAT({ response_format: { type: 'json_schema', json_schema: { name: 'out', schema: { type: 'object', properties: { city: { type: 'string' } } } } } }) });
|
|
365
|
+
const noSchema = await h({ m: 'POST', p: CHAT_PATH, b: CHAT({ response_format: { type: 'json_schema', json_schema: { name: 'out' } } }) });
|
|
366
|
+
const unknown = await h({ m: 'POST', p: CHAT_PATH, b: CHAT({ response_format: { type: 'yaml' } }) });
|
|
367
|
+
const joText = (jo.body as Body).choices[0].message.content as string;
|
|
368
|
+
const jsText = (js.body as Body).choices[0].message.content as string;
|
|
369
|
+
return ok(jo) && ok(js) && noSchema.status === 400 && unknown.status === 400
|
|
370
|
+
&& JSON.parse(joText) && typeof JSON.parse(jsText) === 'object' && (JSON.parse(jsText) as Body).city !== undefined;
|
|
371
|
+
}),
|
|
372
|
+
),
|
|
373
|
+
done('moonshot.chat.tool_result_turn', 'chat', 'Chat: a role:"tool" result turn is accepted and counted into the prompt', 'api', 'common', () =>
|
|
374
|
+
withRoot(async (h) => {
|
|
375
|
+
const r = await h({ m: 'POST', p: CHAT_PATH, b: CHAT({
|
|
376
|
+
messages: [
|
|
377
|
+
{ role: 'user', content: 'weather?' },
|
|
378
|
+
{ role: 'assistant', content: null, tool_calls: [{ id: 'call_1', type: 'function', function: { name: 'get_weather', arguments: '{"city":"Paris"}' } }] },
|
|
379
|
+
{ role: 'tool', tool_call_id: 'call_1', content: '{"temp":21}' },
|
|
380
|
+
],
|
|
381
|
+
}) });
|
|
382
|
+
const bare = await h({ m: 'POST', p: CHAT_PATH, b: CHAT({ messages: [{ role: 'user', content: 'weather?' }] }) });
|
|
383
|
+
return ok(r) && (r.body as Body).usage.prompt_tokens > (bare.body as Body).usage.prompt_tokens;
|
|
384
|
+
}),
|
|
385
|
+
),
|
|
386
|
+
|
|
387
|
+
// ── Streaming (the OpenAI-compatible grammar) ──────────────────────────────────────────
|
|
388
|
+
done('moonshot.streaming.chunk_sequence', 'streaming', 'Streaming: role delta → reasoning delta → content deltas → finish_reason chunk → [DONE]', 'api', 'core', () =>
|
|
389
|
+
withStream(CHAT(), (events) => {
|
|
390
|
+
if (events.length < 4 || !events[events.length - 1]!.done) return false;
|
|
391
|
+
const first = events[0]!.data as Body;
|
|
392
|
+
if (first?.choices?.[0]?.delta?.role !== 'assistant') return false;
|
|
393
|
+
if (first.object !== 'chat.completion.chunk') return false;
|
|
394
|
+
const deltas = events.filter((e) => !e.done && ((e.data as Body)?.choices?.[0]?.delta?.content));
|
|
395
|
+
if (!deltas.length) return false;
|
|
396
|
+
const finish = events.find((e) => !e.done && (e.data as Body)?.choices?.[0]?.finish_reason);
|
|
397
|
+
return !!finish && (finish.data as Body).choices[0].finish_reason === 'stop';
|
|
398
|
+
}),
|
|
399
|
+
),
|
|
400
|
+
done('moonshot.streaming.usage_tail_chunk', 'streaming', "Streaming: Moonshot's usage tail — a FINAL chunk with empty choices[] carrying the whole usage object", 'api', 'core', () =>
|
|
401
|
+
withStream(CHAT(), (events) => {
|
|
402
|
+
const tail = events[events.length - 2]?.data as Body | undefined;
|
|
403
|
+
return !!tail && Array.isArray(tail.choices) && tail.choices.length === 0
|
|
404
|
+
&& typeof tail.usage?.total_tokens === 'number';
|
|
405
|
+
}),
|
|
406
|
+
),
|
|
407
|
+
done('moonshot.streaming.tool_call_deltas', 'streaming', 'Streaming: tool_calls stream as deltas — id+name frame then arguments frame', 'api', 'common', () =>
|
|
408
|
+
withStream(CHAT({ tools: [WEATHER_TOOL] }), (events) => {
|
|
409
|
+
const idFrame = events.find((e) => !e.done && (e.data as Body)?.choices?.[0]?.delta?.tool_calls?.[0]?.id);
|
|
410
|
+
const argsFrame = events.find((e) => !e.done && (e.data as Body)?.choices?.[0]?.delta?.tool_calls?.[0]?.function?.arguments);
|
|
411
|
+
return !!idFrame && !!argsFrame
|
|
412
|
+
&& (idFrame.data as Body).choices[0].delta.tool_calls[0].function.name === 'get_weather';
|
|
413
|
+
}),
|
|
414
|
+
),
|
|
415
|
+
done('moonshot.streaming.reasoning_delta', 'streaming', 'Streaming: reasoning_content streams as its own delta before content', 'api', 'common', () =>
|
|
416
|
+
withStream(CHAT(), (events) => {
|
|
417
|
+
const rIdx = events.findIndex((e) => !e.done && (e.data as Body)?.choices?.[0]?.delta?.reasoning_content);
|
|
418
|
+
const cIdx = events.findIndex((e) => !e.done && (e.data as Body)?.choices?.[0]?.delta?.content);
|
|
419
|
+
return rIdx >= 0 && cIdx > rIdx;
|
|
420
|
+
}),
|
|
421
|
+
),
|
|
422
|
+
|
|
423
|
+
// ── Responses (/v1/responses — kimi-k3 only) ───────────────────────────────────────────
|
|
424
|
+
done('moonshot.responses.create', 'responses', 'Responses: create → the Responses envelope (object response, output items, usage with cached-token details); kimi-k2.x refused', 'api', 'common', () =>
|
|
425
|
+
withRoot(async (h) => {
|
|
426
|
+
const r = await h({ m: 'POST', p: `${MOONSHOT_API_PREFIX}/responses`, b: { model: 'kimi-k3', input: 'hello twin' } });
|
|
427
|
+
const b = r.body as Body;
|
|
428
|
+
if (!ok(r) || b.object !== 'response' || b.status !== 'completed' || !Array.isArray(b.output)) return false;
|
|
429
|
+
if (!b.output.some((o: Body) => o.type === 'message') || !b.output.some((o: Body) => o.type === 'reasoning')) return false;
|
|
430
|
+
if (typeof b.usage?.total_tokens !== 'number' || typeof b.usage?.input_tokens_details?.cached_tokens !== 'number') return false;
|
|
431
|
+
const k26 = await h({ m: 'POST', p: `${MOONSHOT_API_PREFIX}/responses`, b: { model: 'kimi-k2.6', input: 'x' } });
|
|
432
|
+
return k26.status === 400 && String((k26.body as Body).error.message).includes('kimi-k3');
|
|
433
|
+
}),
|
|
434
|
+
),
|
|
435
|
+
done('moonshot.responses.reasoning_effort', 'responses', "Responses: reasoning.effort with the closed set ('low'|'high'|'max')", 'api', 'niche', () =>
|
|
436
|
+
withRoot(async (h) => {
|
|
437
|
+
const low = await h({ m: 'POST', p: `${MOONSHOT_API_PREFIX}/responses`, b: { model: 'kimi-k3', input: 'x', reasoning: { effort: 'low' } } });
|
|
438
|
+
const bad = await h({ m: 'POST', p: `${MOONSHOT_API_PREFIX}/responses`, b: { model: 'kimi-k3', input: 'x', reasoning: { effort: 'medium' } } });
|
|
439
|
+
return ok(low) && bad.status === 400;
|
|
440
|
+
}),
|
|
441
|
+
),
|
|
442
|
+
done('moonshot.responses.max_output_tokens', 'responses', 'Responses: max_output_tokens caps the OUTPUT — status incomplete with reason max_output_tokens, usage.output_tokens counts what was emitted (never above the cap), and a tight cap still emits a non-empty output with output_tokens > 0', 'api', 'niche', () =>
|
|
443
|
+
withRoot(async (h) => {
|
|
444
|
+
const r = await h({ m: 'POST', p: `${MOONSHOT_API_PREFIX}/responses`, b: { model: 'kimi-k3', input: 'produce a long answer please', max_output_tokens: 4 } });
|
|
445
|
+
const b = r.body as Body;
|
|
446
|
+
if (!ok(r) || b.status !== 'incomplete' || b.incomplete_details?.reason !== 'max_output_tokens') return false;
|
|
447
|
+
// THE CAP IS APPLIED, not merely reported: usage counts the emitted (truncated) output —
|
|
448
|
+
// an output_tokens still above the cap contradicts the envelope's own claim.
|
|
449
|
+
if (!(typeof b.usage?.output_tokens === 'number' && b.usage.output_tokens <= 4 && b.usage.output_tokens > 0)) return false;
|
|
450
|
+
if (!Array.isArray(b.output) || b.output.length === 0) return false; // never an empty output
|
|
451
|
+
// output_tokens_details.reasoning_tokens counts the REASONING items only — reporting the
|
|
452
|
+
// whole output_tokens as reasoning (the round-three finding) claimed every text token was
|
|
453
|
+
// reasoning too.
|
|
454
|
+
const reasoning = (b.output as Body[]).filter((o) => o.type === 'reasoning')
|
|
455
|
+
.reduce((n, o) => n + (o.summary as Body[]).reduce((m, s) => m + Math.ceil(String(s.text ?? '').length / 4), 0), 0);
|
|
456
|
+
return b.usage.output_tokens_details?.reasoning_tokens === reasoning;
|
|
457
|
+
}),
|
|
458
|
+
),
|
|
459
|
+
done('moonshot.responses.streaming', 'responses', 'Responses streaming: response.created → output_item.added/done per item → response.completed, sequence_number from 0', 'api', 'common', () =>
|
|
460
|
+
withNamedStream(`${MOONSHOT_API_PREFIX}/responses`, { model: 'kimi-k3', input: 'x' }, (events) => {
|
|
461
|
+
const names = events.filter((e) => !e.done).map((e) => e.event ?? '');
|
|
462
|
+
if (names[0] !== 'response.created' || !names.includes('response.in_progress')) return false;
|
|
463
|
+
if (!names.includes('response.output_item.added') || !names.includes('response.output_item.done')) return false;
|
|
464
|
+
if (names[names.length - 1] !== 'response.completed') return false;
|
|
465
|
+
const seqs = events.filter((e) => !e.done).map((e) => (e.data as Body)?.sequence_number);
|
|
466
|
+
return seqs[0] === 0 && seqs.every((s, i) => s === i);
|
|
467
|
+
}),
|
|
468
|
+
),
|
|
469
|
+
|
|
470
|
+
// ── Anthropic-compatible Messages (/anthropic/v1/messages — kimi-k3 only) ──────────────
|
|
471
|
+
done('moonshot.messages.create', 'messages', 'Messages: create → the Anthropic-compatible envelope (type message, content blocks, stop_reason, usage) with a thinking block first; max_tokens TRUNCATES the emitted content — usage counts it, the message keeps at least one block with output_tokens > 0', 'api', 'common', () =>
|
|
472
|
+
withRoot(async (h) => {
|
|
473
|
+
const r = await h({ m: 'POST', p: MESSAGES_PATH, b: { model: 'kimi-k3', messages: [{ role: 'user', content: 'hello twin' }], max_tokens: 500 } });
|
|
474
|
+
const b = r.body as Body;
|
|
475
|
+
if (!ok(r) || b.type !== 'message' || b.role !== 'assistant' || !Array.isArray(b.content)) return false;
|
|
476
|
+
if (b.content[0]?.type !== 'thinking' || !b.content.some((c: Body) => c.type === 'text')) return false;
|
|
477
|
+
if (b.stop_reason !== 'end_turn' || typeof b.usage?.input_tokens !== 'number') return false;
|
|
478
|
+
// max_tokens REQUIRED (Anthropic grammar).
|
|
479
|
+
const noMax = await h({ m: 'POST', p: MESSAGES_PATH, b: { model: 'kimi-k3', messages: [{ role: 'user', content: 'x' }] } });
|
|
480
|
+
if (noMax.status !== 400) return false;
|
|
481
|
+
// THE CAP IS APPLIED, not merely reported: with a tight cap the emitted content shrinks to
|
|
482
|
+
// fit, usage.output_tokens counts what was emitted, and the message is NEVER empty —
|
|
483
|
+
// Anthropic never returns a zero-block assistant message, so output_tokens 0 / content []
|
|
484
|
+
// at a small cap (the round-three finding) is a lie about what the model produced. Thinking
|
|
485
|
+
// counts toward the cap (Moonshot: output_tokens "including reasoning tokens"), so the
|
|
486
|
+
// tightest caps truncate the leading thinking block — text is asserted present wherever the
|
|
487
|
+
// budget actually reaches the text block.
|
|
488
|
+
for (const cap of [200, 30, 2]) {
|
|
489
|
+
const capped = await h({ m: 'POST', p: MESSAGES_PATH, b: { model: 'kimi-k3', messages: [{ role: 'user', content: 'produce a long answer please' }], max_tokens: cap } });
|
|
490
|
+
const cb = capped.body as Body;
|
|
491
|
+
if (!ok(capped)) return false;
|
|
492
|
+
// The stub's whole answer fits in 200 — only the tighter caps truncate.
|
|
493
|
+
if (cb.stop_reason !== (cap >= 200 ? 'end_turn' : 'max_tokens')) return false;
|
|
494
|
+
if (typeof cb.usage?.output_tokens !== 'number' || cb.usage.output_tokens <= 0 || cb.usage.output_tokens > cap) return false;
|
|
495
|
+
if (!Array.isArray(cb.content) || cb.content.length === 0) return false;
|
|
496
|
+
const text = (cb.content as Body[]).find((c: Body) => c.type === 'text')?.text ?? '';
|
|
497
|
+
if (cap === 30 && !text) return false; // the budget reaches the text block — it must be there
|
|
498
|
+
}
|
|
499
|
+
// Boundary walk (round-four review): a cap that lands exactly on a block boundary, or on a
|
|
500
|
+
// tool_use, must ship a PREFIX — never the whole next block under stop_reason max_tokens.
|
|
501
|
+
// Every cap 20..26 emits <= cap tokens and reports exactly what it emitted.
|
|
502
|
+
const est = (t: string) => Math.max(1, Math.ceil(t.length / 4));
|
|
503
|
+
for (const cap of [20, 21, 22, 23, 24, 25, 26]) {
|
|
504
|
+
const r = await h({ m: 'POST', p: '/anthropic/v1/messages', b: { model: 'kimi-k3', max_tokens: cap, messages: [{ role: 'user', content: 'weather?' }] } });
|
|
505
|
+
const blocks = ((r.body as Body).content ?? []) as Array<{ type: string; text?: string; thinking?: string }>;
|
|
506
|
+
const emitted = blocks.reduce((n, b) => n + est(b.text ?? b.thinking ?? ''), 0);
|
|
507
|
+
if (r.status !== 200 || blocks.length === 0 || emitted > cap || (r.body as Body).usage?.output_tokens !== emitted) return false;
|
|
508
|
+
if ((r.body as Body).stop_reason === 'max_tokens' && emitted > cap) return false;
|
|
509
|
+
}
|
|
510
|
+
const tooled = await h({ m: 'POST', p: '/anthropic/v1/messages', b: { model: 'kimi-k3', max_tokens: 25, messages: [{ role: 'user', content: 'weather?' }], tools: [{ name: 'get_weather', description: 'w', input_schema: { type: 'object', properties: {} } }] } });
|
|
511
|
+
const tb = ((tooled.body as Body).content ?? []) as Array<{ type: string }>;
|
|
512
|
+
if ((tooled.body as Body).stop_reason === 'max_tokens' && tb.some((b) => b.type === 'tool_use')) return false;
|
|
513
|
+
return true;
|
|
514
|
+
}),
|
|
515
|
+
),
|
|
516
|
+
done('moonshot.messages.tool_use', 'messages', 'Messages: tools + tool_choice are REAL surface — any/auto yield a tool_use block (toolu_ id), a named {type:tool} call is honored, none suppresses, a follow-up tool_result turn answers end_turn text, and tool_choice without tools / a bad name / a named tool missing from tools are 400s', 'api', 'common', () =>
|
|
517
|
+
withRoot(async (h) => {
|
|
518
|
+
const WEATHER = { type: 'custom', name: 'get_weather', input_schema: { type: 'object', properties: { city: { type: 'string' } } } };
|
|
519
|
+
const any = await h({ m: 'POST', p: MESSAGES_PATH, b: { model: 'kimi-k3', messages: [{ role: 'user', content: 'weather?' }], max_tokens: 500, tools: [WEATHER], tool_choice: { type: 'any' } } });
|
|
520
|
+
const ab = any.body as Body;
|
|
521
|
+
const toolUse = (ab.content as Body[])?.find((c: Body) => c.type === 'tool_use');
|
|
522
|
+
if (!ok(any) || ab.stop_reason !== 'tool_use' || !toolUse) return false;
|
|
523
|
+
if (toolUse.name !== 'get_weather' || typeof toolUse.id !== 'string' || typeof toolUse.input !== 'object') return false;
|
|
524
|
+
if (!String(toolUse.id).startsWith('toolu_')) return false; // Anthropic's id grammar
|
|
525
|
+
// A NAMED tool_choice calls THAT tool (the SDK's ToolChoice union; the round-three 400).
|
|
526
|
+
const named = await h({ m: 'POST', p: MESSAGES_PATH, b: { model: 'kimi-k3', messages: [{ role: 'user', content: 'weather?' }], max_tokens: 500, tools: [WEATHER], tool_choice: { type: 'tool', name: 'get_weather' } } });
|
|
527
|
+
const namedUse = ((named.body as Body).content as Body[])?.find((c: Body) => c.type === 'tool_use');
|
|
528
|
+
if (!ok(named) || namedUse?.name !== 'get_weather') return false;
|
|
529
|
+
const none = await h({ m: 'POST', p: MESSAGES_PATH, b: { model: 'kimi-k3', messages: [{ role: 'user', content: 'x' }], max_tokens: 500, tools: [WEATHER], tool_choice: { type: 'none' } } });
|
|
530
|
+
const nb = none.body as Body;
|
|
531
|
+
if (!ok(none) || nb.stop_reason !== 'end_turn' || (nb.content as Body[])?.some((c: Body) => c.type === 'tool_use')) return false;
|
|
532
|
+
// The AGENT LOOP closes: after the tool_result comes back, the next turn answers TEXT with
|
|
533
|
+
// end_turn — deciding tool_use from tools.length alone made the loop emit tool_use forever.
|
|
534
|
+
const followUp = await h({ m: 'POST', p: MESSAGES_PATH, b: {
|
|
535
|
+
model: 'kimi-k3', max_tokens: 500, tools: [WEATHER],
|
|
536
|
+
messages: [
|
|
537
|
+
{ role: 'user', content: 'weather?' },
|
|
538
|
+
{ role: 'assistant', content: [{ type: 'tool_use', id: String(toolUse.id), name: 'get_weather', input: { city: 'Oslo' } }] },
|
|
539
|
+
{ role: 'user', content: [{ type: 'tool_result', tool_use_id: String(toolUse.id), content: '18°C, clear' }] },
|
|
540
|
+
],
|
|
541
|
+
} });
|
|
542
|
+
const fb = followUp.body as Body;
|
|
543
|
+
if (!ok(followUp) || fb.stop_reason !== 'end_turn') return false;
|
|
544
|
+
if ((fb.content as Body[])?.some((c: Body) => c.type === 'tool_use')) return false;
|
|
545
|
+
if (!((fb.content as Body[])?.some((c: Body) => c.type === 'text'))) return false;
|
|
546
|
+
const noTools = await h({ m: 'POST', p: MESSAGES_PATH, b: { model: 'kimi-k3', messages: [{ role: 'user', content: 'x' }], max_tokens: 500, tool_choice: { type: 'any' } } });
|
|
547
|
+
const badName = await h({ m: 'POST', p: MESSAGES_PATH, b: { model: 'kimi-k3', messages: [{ role: 'user', content: 'x' }], max_tokens: 500, tools: [{ type: 'custom', name: '9bad name', input_schema: {} }] } });
|
|
548
|
+
// A named choice naming a tool that is NOT in tools is refused (the sibling's rule).
|
|
549
|
+
const ghost = await h({ m: 'POST', p: MESSAGES_PATH, b: { model: 'kimi-k3', messages: [{ role: 'user', content: 'x' }], max_tokens: 500, tools: [WEATHER], tool_choice: { type: 'tool', name: 'not_offered' } } });
|
|
550
|
+
return noTools.status === 400 && badName.status === 400 && ghost.status === 400
|
|
551
|
+
&& (noTools.body as Body).type === 'error' && (badName.body as Body).type === 'error' && (ghost.body as Body).type === 'error';
|
|
552
|
+
}),
|
|
553
|
+
),
|
|
554
|
+
done('moonshot.messages.kimi_k3_only', 'messages', 'Messages: kimi-k2.x refused with the Messages error envelope', 'api', 'common', () =>
|
|
555
|
+
withRoot(async (h) => {
|
|
556
|
+
const r = await h({ m: 'POST', p: MESSAGES_PATH, b: { model: 'kimi-k2.6', messages: [{ role: 'user', content: 'x' }], max_tokens: 10 } });
|
|
557
|
+
const b = r.body as Body;
|
|
558
|
+
return r.status === 400 && b.type === 'error' && typeof b.error?.type === 'string' && typeof b.error?.message === 'string';
|
|
559
|
+
}),
|
|
560
|
+
),
|
|
561
|
+
done('moonshot.messages.system_and_stop', 'messages', 'Messages: top-level system prompt, stop_sequences (≤5, ≤32 bytes) with stop_sequence reported', 'api', 'niche', () =>
|
|
562
|
+
withRoot(async (h) => {
|
|
563
|
+
const sys = await h({ m: 'POST', p: MESSAGES_PATH, b: { model: 'kimi-k3', system: 'Be terse.', messages: [{ role: 'user', content: 'hi' }], max_tokens: 500 } });
|
|
564
|
+
const stop = await h({ 'm': 'POST', p: MESSAGES_PATH, b: { model: 'kimi-k3', messages: [{ role: 'user', content: 'hi' }], max_tokens: 500, stop_sequences: ['Echoing'] } });
|
|
565
|
+
const tooMany = await h({ m: 'POST', p: MESSAGES_PATH, b: { model: 'kimi-k3', messages: [{ role: 'user', content: 'x' }], max_tokens: 10, stop_sequences: ['1', '2', '3', '4', '5', '6'] } });
|
|
566
|
+
const b = stop.body as Body;
|
|
567
|
+
return ok(sys) && ok(stop) && tooMany.status === 400
|
|
568
|
+
&& b.stop_sequence === 'Echoing' && !(b.content.find((c: Body) => c.type === 'text')?.text ?? '').includes('Echoing');
|
|
569
|
+
}),
|
|
570
|
+
),
|
|
571
|
+
done('moonshot.messages.error_envelope', 'messages', 'Messages errors — including the CROSS-CUTTING ones (401 auth, 429 rate limit, 503 outage, 405 read-only) — answer the Anthropic-compatible envelope {type:error, error:{type,message}} with Anthropic error types, never the /v1 shape', 'api', 'common', () =>
|
|
572
|
+
withRootH(async (h) => {
|
|
573
|
+
// The validation 400…
|
|
574
|
+
const noMax = await h({ m: 'POST', p: MESSAGES_PATH, b: { model: 'kimi-k3', messages: [{ role: 'user', content: 'x' }] } });
|
|
575
|
+
if (noMax.status !== 400) return false;
|
|
576
|
+
const isAnthropicError = (r: MoonshotResponseEnvelope): boolean => {
|
|
577
|
+
const b = r.body as Body;
|
|
578
|
+
return b?.type === 'error' && typeof b.error?.type === 'string' && typeof b.error?.message === 'string'
|
|
579
|
+
&& !('code' in (b.error ?? {}));
|
|
580
|
+
};
|
|
581
|
+
if (!isAnthropicError(noMax) || (noMax.body as Body).error?.type !== 'invalid_request_error') return false;
|
|
582
|
+
// …and every cross-cutting failure BEFORE routing: auth (x-api-key, the SDK's own header),
|
|
583
|
+
// the two deterministic fault triggers, and the read-only 405 — each of which used to leak
|
|
584
|
+
// the /v1 envelope regardless of prefix (the round-two finding).
|
|
585
|
+
const auth = await h({ m: 'POST', p: MESSAGES_PATH, b: { model: 'kimi-k3', messages: [{ role: 'user', content: 'x' }], max_tokens: 10 }, headers: { 'x-api-key': '' } });
|
|
586
|
+
if (auth.status !== 401 || !isAnthropicError(auth) || (auth.body as Body).error?.type !== 'authentication_error') return false;
|
|
587
|
+
const rate = await h({ m: 'POST', p: MESSAGES_PATH, b: { model: 'kimi-k3', messages: [{ role: 'user', content: 'x' }], max_tokens: 10 }, headers: { 'x-api-key': 'sk-twin', 'x-twin-force-rate-limit': '1' } });
|
|
588
|
+
if (rate.status !== 429 || !isAnthropicError(rate) || (rate.body as Body).error?.type !== 'rate_limit_error') return false;
|
|
589
|
+
const out = await h({ m: 'POST', p: MESSAGES_PATH, b: { model: 'kimi-k3', messages: [{ role: 'user', content: 'x' }], max_tokens: 10 }, headers: { 'x-api-key': 'sk-twin', 'x-twin-force-server-unavailable': '1' } });
|
|
590
|
+
if (out.status !== 503 || !isAnthropicError(out) || (out.body as Body).error?.type !== 'overloaded_error') return false;
|
|
591
|
+
const root = mkdtempSync(join(tmpdir(), 'moonshot-cap-ro-'));
|
|
592
|
+
try {
|
|
593
|
+
const ro = await handleMoonshotTwinRequest({ method: 'POST', path: MESSAGES_PATH, body: JSON.stringify({ model: 'kimi-k3', messages: [{ role: 'user', content: 'x' }], max_tokens: 10 }), root, readOnly: true, headers: { 'x-api-key': 'sk-twin' } });
|
|
594
|
+
if (ro.status !== 405 || !isAnthropicError(ro)) return false;
|
|
595
|
+
} finally {
|
|
596
|
+
rmSync(root, { recursive: true, force: true });
|
|
597
|
+
}
|
|
598
|
+
return true;
|
|
599
|
+
}),
|
|
600
|
+
),
|
|
601
|
+
done('moonshot.messages.streaming', 'messages', 'Messages streaming: message_start carries the EMPTY message (content [], stop_reason null, output_tokens 0) → ping → one block at a time → message_delta (stop_reason + final usage) → message_stop (no [DONE])', 'api', 'common', () =>
|
|
602
|
+
withNamedStream(MESSAGES_PATH, { model: 'kimi-k3', messages: [{ role: 'user', content: 'x' }], max_tokens: 200, stream: true }, (events) => {
|
|
603
|
+
const names = events.filter((e) => !e.done).map((e) => e.event ?? '');
|
|
604
|
+
if (names[0] !== 'message_start' || names[names.length - 1] !== 'message_stop') return false;
|
|
605
|
+
if (!names.includes('content_block_delta') || !names.includes('content_block_stop')) return false;
|
|
606
|
+
if (!names.includes('message_delta') || !names.includes('ping')) return false;
|
|
607
|
+
const thinkingDelta = events.find((e) => (e.data as Body)?.delta?.type === 'thinking_delta');
|
|
608
|
+
if (!thinkingDelta) return false;
|
|
609
|
+
// THE GRAMMAR: message_start holds an EMPTY message — a complete final message there made
|
|
610
|
+
// the SDK's own accumulator double every block (the round-three BLOCKER).
|
|
611
|
+
const start = events.find((e) => e.event === 'message_start')?.data as Body;
|
|
612
|
+
const msg = start?.message as Body;
|
|
613
|
+
if (!Array.isArray(msg?.content) || msg.content.length !== 0) return false;
|
|
614
|
+
if (msg.stop_reason !== null || msg.usage?.output_tokens !== 0) return false;
|
|
615
|
+
// message_delta carries the final stop_reason and the final output_tokens.
|
|
616
|
+
const delta = events.find((e) => e.event === 'message_delta')?.data as Body;
|
|
617
|
+
if (delta?.delta?.stop_reason !== 'end_turn' || (delta?.usage?.output_tokens ?? 0) <= 0) return false;
|
|
618
|
+
// Every content_block_start index matches a distinct block; no index repeats after its stop.
|
|
619
|
+
const starts = events.filter((e) => e.event === 'content_block_start').map((e) => (e.data as Body).index);
|
|
620
|
+
if (new Set(starts).size !== starts.length) return false;
|
|
621
|
+
return names[names.length - 2] === 'message_delta';
|
|
622
|
+
}),
|
|
623
|
+
),
|
|
624
|
+
|
|
625
|
+
// ── Models ─────────────────────────────────────────────────────────────────────────────
|
|
626
|
+
done('moonshot.models.list', 'models', 'Models: list → the static catalog (kimi-k3, kimi-k2.7-code, kimi-k2.7-code-highspeed, kimi-k2.6)', 'api', 'core', () =>
|
|
627
|
+
withRoot(async (h) => {
|
|
628
|
+
const r = await h({ m: 'GET', p: `${MOONSHOT_API_PREFIX}/models` });
|
|
629
|
+
const data = (r.body as Body).data as Body[];
|
|
630
|
+
const ids = data.map((m) => m.id).sort();
|
|
631
|
+
return ok(r) && (r.body as Body).object === 'list'
|
|
632
|
+
&& JSON.stringify(ids) === JSON.stringify(['kimi-k2.6', 'kimi-k2.7-code', 'kimi-k2.7-code-highspeed', 'kimi-k3']);
|
|
633
|
+
}),
|
|
634
|
+
),
|
|
635
|
+
done('moonshot.models.retired_absent', 'models', 'Models: retired ids (moonshot-v1-*, kimi-latest, kimi-k2) are NOT served — a stale client must fail here like it fails at the vendor', 'api', 'common', () =>
|
|
636
|
+
withRoot(async (h) => {
|
|
637
|
+
const r = await h({ m: 'GET', p: `${MOONSHOT_API_PREFIX}/models` });
|
|
638
|
+
const ids = ((r.body as Body).data as Body[]).map((m) => m.id);
|
|
639
|
+
if (ids.some((i) => i.startsWith('moonshot-v1') || i === 'kimi-latest' || i.startsWith('kimi-k2-'))) return false;
|
|
640
|
+
const old = await h({ m: 'POST', p: CHAT_PATH, b: CHAT({ model: 'moonshot-v1-8k' }) });
|
|
641
|
+
return old.status === 400;
|
|
642
|
+
}),
|
|
643
|
+
),
|
|
644
|
+
todo('moonshot.models.retrieve', 'models', "Models: retrieve by id — Moonshot's own OpenAPI declares NO /v1/models/{model} operation (the served 200 was a 200 for an unmodeled route, the round-two finding); the twin now 404s like the vendor, and this entry records the gap until the vendor publishes the operation", 'api', 'core'),
|
|
645
|
+
|
|
646
|
+
// ── Files (stateful) ───────────────────────────────────────────────────────────────────
|
|
647
|
+
done('moonshot.files.create', 'files', 'Files: create → a stateful File object (purpose closed set, status ready)', 'api', 'common', () =>
|
|
648
|
+
withRoot(async (h) => {
|
|
649
|
+
const r = await h({ m: 'POST', p: `${MOONSHOT_API_PREFIX}/files`, b: { purpose: 'batch', filename: 'in.jsonl', content: '{"custom_id":"a"}' } });
|
|
650
|
+
const b = r.body as Body;
|
|
651
|
+
if (!ok(r) || b.object !== 'file' || b.purpose !== 'batch' || b.status !== 'ready' || typeof b.bytes !== 'number') return false;
|
|
652
|
+
if (!String(b.id).startsWith('file_twin_')) return false;
|
|
653
|
+
const badPurpose = await h({ m: 'POST', p: `${MOONSHOT_API_PREFIX}/files`, b: { purpose: 'fine-tune', filename: 'x', content: 'x' } });
|
|
654
|
+
return badPurpose.status === 400 && String((badPurpose.body as Body).error.message).includes('purpose');
|
|
655
|
+
}),
|
|
656
|
+
),
|
|
657
|
+
done('moonshot.files.list_retrieve', 'files', 'Files: list + retrieve round-trip; unknown id 404', 'api', 'common', () =>
|
|
658
|
+
withRoot(async (h) => {
|
|
659
|
+
const f = await h({ m: 'POST', p: `${MOONSHOT_API_PREFIX}/files`, b: { purpose: 'file-extract', filename: 'doc.txt', content: 'hello' } });
|
|
660
|
+
const list = await h({ m: 'GET', p: `${MOONSHOT_API_PREFIX}/files` });
|
|
661
|
+
const one = await h({ m: 'GET', p: `${MOONSHOT_API_PREFIX}/files/${id(f)}` });
|
|
662
|
+
const miss = await h({ m: 'GET', p: `${MOONSHOT_API_PREFIX}/files/file_does_not_exist` });
|
|
663
|
+
return ok(f) && ok(list) && (list.body as Body).object === 'list'
|
|
664
|
+
&& ((list.body as Body).data as Body[]).some((x) => x.id === id(f))
|
|
665
|
+
&& ok(one) && (one.body as Body).filename === 'doc.txt' && miss.status === 404;
|
|
666
|
+
}),
|
|
667
|
+
),
|
|
668
|
+
done('moonshot.files.content', 'files', 'Files: content returns the stored bytes verbatim', 'api', 'common', () =>
|
|
669
|
+
withRoot(async (h) => {
|
|
670
|
+
const f = await h({ m: 'POST', p: `${MOONSHOT_API_PREFIX}/files`, b: { purpose: 'batch', filename: 'in.jsonl', content: '{"custom_id":"a"}\n{"custom_id":"b"}' } });
|
|
671
|
+
const c = await h({ m: 'GET', p: `${MOONSHOT_API_PREFIX}/files/${id(f)}/content` });
|
|
672
|
+
return ok(c) && c.body === '{"custom_id":"a"}\n{"custom_id":"b"}';
|
|
673
|
+
}),
|
|
674
|
+
),
|
|
675
|
+
done('moonshot.files.delete_id_ratchets', 'files', 'Files: delete → {deleted:true}; the id is never re-minted after delete (dirty-state proof)', 'api', 'common', () =>
|
|
676
|
+
withRoot(async (h, root) => {
|
|
677
|
+
const f = await h({ m: 'POST', p: `${MOONSHOT_API_PREFIX}/files`, b: { purpose: 'batch', filename: 'one.jsonl', content: 'x' } });
|
|
678
|
+
const del = await h({ m: 'DELETE', p: `${MOONSHOT_API_PREFIX}/files/${id(f)}` });
|
|
679
|
+
if (!ok(del) || (del.body as Body).deleted !== true) return false;
|
|
680
|
+
// The tombstoned row keeps its id: a recreate must mint the NEXT number, never this one.
|
|
681
|
+
const f2 = await h({ m: 'POST', p: `${MOONSHOT_API_PREFIX}/files`, b: { purpose: 'batch', filename: 'two.jsonl', content: 'x' } });
|
|
682
|
+
const after = projectResources('moonshot', root).filter((r) => r.type === 'file');
|
|
683
|
+
return ok(f2) && id(f2) !== id(f) && after.filter((r) => r.id === id(f)).length === 1;
|
|
684
|
+
}),
|
|
685
|
+
),
|
|
686
|
+
done('moonshot.files.pulled_content_refused', 'files', 'Files: a PULLED file (metadata only — the vendor list endpoint returns no content) answers 501 on /content instead of a fake empty 200', 'api', 'common', () =>
|
|
687
|
+
withConnectorRoot('moonshot.files.pulled_content_refused', async (root) => {
|
|
688
|
+
const { execute } = fakeExecute((m, p) => {
|
|
689
|
+
if (p === '/v1/files') return { data: [{ id: 'file_real_1', object: 'file', bytes: 3, created_at: 1, filename: 'real.jsonl', purpose: 'batch', status: 'ready' }] };
|
|
690
|
+
return { data: [] };
|
|
691
|
+
});
|
|
692
|
+
const pulled = await syncMoonshotFromReal(execute, { root, occurredAt: '2026-06-15T00:00:00Z' });
|
|
693
|
+
if (pulled.observed < 1) return false;
|
|
694
|
+
const c = await handleMoonshotTwinRequest({ method: 'GET', path: `${MOONSHOT_API_PREFIX}/files/file_real_1/content`, root });
|
|
695
|
+
return c.status === 501 && errType(c) === 'server_error' && String((c.body as Body).error.message).includes('holds no content');
|
|
696
|
+
}),
|
|
697
|
+
),
|
|
698
|
+
|
|
699
|
+
// ── Batches (stateful) ─────────────────────────────────────────────────────────────────
|
|
700
|
+
done('moonshot.batches.create', 'batches', 'Batches: create over a batch-purpose file → a stateful Batch (endpoint closed set, completion_window 12h–7d, expires_at derived)', 'api', 'common', () =>
|
|
701
|
+
withRoot(async (h) => {
|
|
702
|
+
const bid = await seedBatch(h);
|
|
703
|
+
if (!bid) return false;
|
|
704
|
+
const b = (await h({ m: 'GET', p: `${MOONSHOT_API_PREFIX}/batches/${bid}` })).body as Body;
|
|
705
|
+
if (b.status !== 'validating' || b.endpoint !== '/v1/chat/completions') return false;
|
|
706
|
+
if (typeof b.expires_at !== 'number' || b.expires_at <= b.created_at) return false;
|
|
707
|
+
// The closed sets: endpoint and window.
|
|
708
|
+
const badEndpoint = await h({ m: 'POST', p: `${MOONSHOT_API_PREFIX}/batches`, b: { input_file_id: 'file_twin_1', endpoint: '/v1/embeddings', completion_window: '24h' } });
|
|
709
|
+
const badWindow = await h({ m: 'POST', p: `${MOONSHOT_API_PREFIX}/batches`, b: { input_file_id: 'file_twin_1', endpoint: '/v1/chat/completions', completion_window: '6h' } });
|
|
710
|
+
return badEndpoint.status === 400 && badWindow.status === 400;
|
|
711
|
+
}),
|
|
712
|
+
),
|
|
713
|
+
done('moonshot.batches.file_validation', 'batches', 'Batches: input_file_id must reference an existing, batch-purpose file', 'api', 'common', () =>
|
|
714
|
+
withRoot(async (h) => {
|
|
715
|
+
const missing = await h({ m: 'POST', p: `${MOONSHOT_API_PREFIX}/batches`, b: { input_file_id: 'file_nope', endpoint: '/v1/chat/completions', completion_window: '24h' } });
|
|
716
|
+
const f = await h({ m: 'POST', p: `${MOONSHOT_API_PREFIX}/files`, b: { purpose: 'file-extract', filename: 'doc.txt', content: 'x' } });
|
|
717
|
+
const wrongPurpose = await h({ m: 'POST', p: `${MOONSHOT_API_PREFIX}/batches`, b: { input_file_id: id(f), endpoint: '/v1/chat/completions', completion_window: '24h' } });
|
|
718
|
+
return missing.status === 404 && wrongPurpose.status === 400
|
|
719
|
+
&& String((wrongPurpose.body as Body).error.message).includes("purpose 'batch'");
|
|
720
|
+
}),
|
|
721
|
+
),
|
|
722
|
+
done('moonshot.batches.list_retrieve_cancel', 'batches', 'Batches: list, retrieve, cancel (cancelling, cancelling_at set, double cancel refused)', 'api', 'common', () =>
|
|
723
|
+
withRoot(async (h) => {
|
|
724
|
+
const bid = await seedBatch(h);
|
|
725
|
+
if (!bid) return false;
|
|
726
|
+
const list = await h({ m: 'GET', p: `${MOONSHOT_API_PREFIX}/batches` });
|
|
727
|
+
const cancel = await h({ m: 'POST', p: `${MOONSHOT_API_PREFIX}/batches/${bid}/cancel` });
|
|
728
|
+
const c = cancel.body as Body;
|
|
729
|
+
const again = await h({ m: 'POST', p: `${MOONSHOT_API_PREFIX}/batches/${bid}/cancel` });
|
|
730
|
+
return ok(list) && ((list.body as Body).data as Body[]).some((x) => x.id === bid)
|
|
731
|
+
&& ok(cancel) && c.status === 'cancelling' && typeof c.cancelling_at === 'number'
|
|
732
|
+
&& again.status === 400 && String((again.body as Body).error.message).includes('cancelling');
|
|
733
|
+
}),
|
|
734
|
+
),
|
|
735
|
+
|
|
736
|
+
// ── Balance (Moonshot's own envelope) ──────────────────────────────────────────────────
|
|
737
|
+
done('moonshot.balance.envelope', 'balance', 'Balance: GET /v1/users/me/balance answers Moonshot\'s OWN envelope {code,data:{available,voucher,cash},scode,status} — not the OpenAI envelope', 'api', 'common', () =>
|
|
738
|
+
withRoot(async (h) => {
|
|
739
|
+
const r = await h({ m: 'GET', p: `${MOONSHOT_API_PREFIX}/users/me/balance` });
|
|
740
|
+
const b = r.body as Body;
|
|
741
|
+
return ok(r) && b.code === 0 && b.scode === '0x0' && b.status === true
|
|
742
|
+
&& typeof b.data?.available_balance === 'number' && typeof b.data?.voucher_balance === 'number' && typeof b.data?.cash_balance === 'number';
|
|
743
|
+
}),
|
|
744
|
+
),
|
|
745
|
+
|
|
746
|
+
// ── Stateless helpers ──────────────────────────────────────────────────────────────────
|
|
747
|
+
done('moonshot.tokens.estimate', 'tokens', 'Token counting: POST /v1/tokenizers/estimate-token-count → {data:{total_tokens}}, model validated, empty content refused', 'api', 'common', () =>
|
|
748
|
+
withRoot(async (h) => {
|
|
749
|
+
const r = await h({ m: 'POST', p: `${MOONSHOT_API_PREFIX}/tokenizers/estimate-token-count`, b: { model: 'kimi-k3', messages: [{ role: 'user', content: 'hello world, estimate me' }] } });
|
|
750
|
+
const badModel = await h({ m: 'POST', p: `${MOONSHOT_API_PREFIX}/tokenizers/estimate-token-count`, b: { model: 'nope', messages: [{ role: 'user', content: 'x' }] } });
|
|
751
|
+
const empty = await h({ m: 'POST', p: `${MOONSHOT_API_PREFIX}/tokenizers/estimate-token-count`, b: { model: 'kimi-k3', messages: [{ role: 'user', content: '' }] } });
|
|
752
|
+
const b = r.body as Body;
|
|
753
|
+
return ok(r) && typeof b.data?.total_tokens === 'number' && b.data.total_tokens > 0
|
|
754
|
+
&& badModel.status === 400 && empty.status === 400;
|
|
755
|
+
}),
|
|
756
|
+
),
|
|
757
|
+
done('moonshot.tokens.matches_chat_usage', 'tokens', 'Token counting: the estimate equals the chat endpoint prompt_tokens for the same input (one tokenizer, two doors)', 'api', 'common', () =>
|
|
758
|
+
withRoot(async (h) => {
|
|
759
|
+
const messages = [{ role: 'system', content: 'You are terse.' }, { role: 'user', content: 'count these tokens precisely' }];
|
|
760
|
+
const est = await h({ m: 'POST', p: `${MOONSHOT_API_PREFIX}/tokenizers/estimate-token-count`, b: { model: 'kimi-k3', messages } });
|
|
761
|
+
const chat = await h({ m: 'POST', p: CHAT_PATH, b: { model: 'kimi-k3', messages } });
|
|
762
|
+
return ok(est) && ok(chat)
|
|
763
|
+
&& (est.body as Body).data.total_tokens === (chat.body as Body).usage.prompt_tokens;
|
|
764
|
+
}),
|
|
765
|
+
),
|
|
766
|
+
done('moonshot.signatures.verify', 'signatures', 'Signature verify: a tuple recomputing to the twin\'s own signature is valid:true; a wrong/foreign-prefix signature is valid:false; missing fields 400', 'api', 'niche', () =>
|
|
767
|
+
withRootH(async (h) => {
|
|
768
|
+
const nonce = 'nonce-abc', ts = 1_786_338_000_123, model = 'kimi-k2.7-code';
|
|
769
|
+
const sig = messagesSignature(nonce, ts, model);
|
|
770
|
+
const good = await h({ m: 'POST', p: `${MOONSHOT_API_PREFIX}/signatures/verify`, b: { nonce, timestamp: ts, model, signature: sig } });
|
|
771
|
+
const wrong = await h({ m: 'POST', p: `${MOONSHOT_API_PREFIX}/signatures/verify`, b: { nonce, timestamp: ts, model, signature: 'reqsigv1_tampered' } });
|
|
772
|
+
const foreign = await h({ m: 'POST', p: `${MOONSHOT_API_PREFIX}/signatures/verify`, b: { nonce, timestamp: ts, model, signature: 'sig-no-prefix' } });
|
|
773
|
+
const missing = await h({ m: 'POST', p: `${MOONSHOT_API_PREFIX}/signatures/verify`, b: { nonce, timestamp: ts, model } });
|
|
774
|
+
return ok(good) && (good.body as Body).valid === true
|
|
775
|
+
&& ok(wrong) && (wrong.body as Body).valid === false
|
|
776
|
+
&& ok(foreign) && (foreign.body as Body).valid === false
|
|
777
|
+
&& missing.status === 400;
|
|
778
|
+
}),
|
|
779
|
+
),
|
|
780
|
+
done('moonshot.signatures.round_trip', 'signatures', 'Signature round trip: a STREAMED model call carrying X-Msh-Request-Nonce emits Msh-Request-* headers whose signature verifies valid:true; a nonce-less call emits none', 'api', 'niche', () =>
|
|
781
|
+
withRootH(async (h) => {
|
|
782
|
+
// The composition the two halves never proved together (§9 round two, F8): the headers
|
|
783
|
+
// the SERVER-side contract mints (via messagesSignature over the request's nonce and
|
|
784
|
+
// worldNow) fed back through POST /v1/signatures/verify. Driven through the handler's
|
|
785
|
+
// SSE door so the same code path the server exercises runs here.
|
|
786
|
+
const events: unknown[] = [];
|
|
787
|
+
const signed = await handleMoonshotTwinRequest({
|
|
788
|
+
method: 'POST', path: CHAT_PATH, body: JSON.stringify(CHAT({ stream: true })),
|
|
789
|
+
headers: { authorization: 'Bearer sk-twin', 'x-msh-request-nonce': 'rt-nonce-1' },
|
|
790
|
+
sseSink: (e) => events.push(e),
|
|
791
|
+
});
|
|
792
|
+
const ts = signed.headers?.['msh-request-timestamp'];
|
|
793
|
+
const sig = signed.headers?.['msh-request-signature'];
|
|
794
|
+
if (!ts || !sig) return false;
|
|
795
|
+
const check = await h({ m: 'POST', p: `${MOONSHOT_API_PREFIX}/signatures/verify`, b: { nonce: 'rt-nonce-1', timestamp: Number(ts), model: CHAT().model, signature: sig } });
|
|
796
|
+
if (!ok(check) || (check.body as Body).valid !== true) return false;
|
|
797
|
+
// A nonce-less streamed call proceeds UNSIGNED (the round-one fix).
|
|
798
|
+
events.length = 0;
|
|
799
|
+
const unsigned = await handleMoonshotTwinRequest({
|
|
800
|
+
method: 'POST', path: CHAT_PATH, body: JSON.stringify(CHAT({ stream: true })),
|
|
801
|
+
headers: { authorization: 'Bearer sk-twin' },
|
|
802
|
+
sseSink: (e) => events.push(e),
|
|
803
|
+
});
|
|
804
|
+
return events.length > 0 && unsigned.headers?.['msh-request-signature'] === undefined;
|
|
805
|
+
}),
|
|
806
|
+
),
|
|
807
|
+
done('moonshot.tools.search', 'tools', 'Web search: POST /v1/tools/search → labeled deterministic results, limit 1..20 enforced; include_content:true SERVES page text and the default leaves text empty', 'api', 'common', () =>
|
|
808
|
+
withRoot(async (h) => {
|
|
809
|
+
const r = await h({ m: 'POST', p: `${MOONSHOT_API_PREFIX}/tools/search`, b: { text_query: 'kimi api limits', limit: 3 } });
|
|
810
|
+
const badLimit = await h({ m: 'POST', p: `${MOONSHOT_API_PREFIX}/tools/search`, b: { text_query: 'x', limit: 21 } });
|
|
811
|
+
const noQuery = await h({ m: 'POST', p: `${MOONSHOT_API_PREFIX}/tools/search`, b: {} });
|
|
812
|
+
const withContent = await h({ m: 'POST', p: `${MOONSHOT_API_PREFIX}/tools/search`, b: { text_query: 'kimi api limits', limit: 2, include_content: true } });
|
|
813
|
+
const results = (r.body as Body).search_results as Body[];
|
|
814
|
+
const contentResults = (withContent.body as Body).search_results as Body[];
|
|
815
|
+
return ok(r) && results.length === 3 && typeof results[0]?.url === 'string' && typeof results[0]?.snippet === 'string'
|
|
816
|
+
&& JSON.stringify(results[0]).includes('[twin-stub]')
|
|
817
|
+
&& results.every((x) => x.text === '')
|
|
818
|
+
&& ok(withContent) && contentResults.length === 2 && contentResults.every((x) => typeof x.text === 'string' && x.text.includes('[twin-stub]'))
|
|
819
|
+
&& badLimit.status === 400 && noQuery.status === 400;
|
|
820
|
+
}),
|
|
821
|
+
),
|
|
822
|
+
done('moonshot.tools.search_pro', 'tools', 'search_pro: sites (≤5, no whitespace/parens) + time_window (YYYY[-MM[-DD]]) validated; results carry chunks (pinned on the first result)', 'api', 'common', () =>
|
|
823
|
+
withRoot(async (h) => {
|
|
824
|
+
const r = await h({ m: 'POST', p: `${MOONSHOT_API_PREFIX}/tools/search_pro`, b: { text_query: 'kimi', sites: ['platform.kimi.ai'], time_window: { start: '2026-01', end: '2026-09-16' } } });
|
|
825
|
+
const badSite = await h({ m: 'POST', p: `${MOONSHOT_API_PREFIX}/tools/search_pro`, b: { text_query: 'x', sites: ['a b'] } });
|
|
826
|
+
const badWindow = await h({ m: 'POST', p: `${MOONSHOT_API_PREFIX}/tools/search_pro`, b: { text_query: 'x', time_window: { start: 'Jan 2026' } } });
|
|
827
|
+
const results = (r.body as Body).search_results as Body[];
|
|
828
|
+
return ok(r) && results.length === 5 && Array.isArray(results[0]?.chunks)
|
|
829
|
+
&& badSite.status === 400 && badWindow.status === 400;
|
|
830
|
+
}),
|
|
831
|
+
),
|
|
832
|
+
done('moonshot.tools.fetch', 'tools', 'fetch: POST /v1/tools/fetch → labeled deterministic markdown for an http(s) URL; a non-http URL is a 400', 'api', 'common', () =>
|
|
833
|
+
withRoot(async (h) => {
|
|
834
|
+
const r = await h({ m: 'POST', p: `${MOONSHOT_API_PREFIX}/tools/fetch`, b: { url: 'https://platform.kimi.ai/docs' } });
|
|
835
|
+
const bad = await h({ m: 'POST', p: `${MOONSHOT_API_PREFIX}/tools/fetch`, b: { url: 'ftp://example.test/x' } });
|
|
836
|
+
return ok(r) && (r.body as Body).url === 'https://platform.kimi.ai/docs'
|
|
837
|
+
&& typeof (r.body as Body).markdown === 'string' && (r.body as Body).markdown.includes('[twin-stub]')
|
|
838
|
+
&& bad.status === 400;
|
|
839
|
+
}),
|
|
840
|
+
),
|
|
841
|
+
|
|
842
|
+
// ── Auth (modeled 401s) ────────────────────────────────────────────────────────────────
|
|
843
|
+
done('moonshot.auth.missing_key', 'auth', 'Auth: a request carrying headers but no credential → 401 invalid_authentication_error', 'api', 'core', () =>
|
|
844
|
+
withRootH(async (h) => {
|
|
845
|
+
const r = await h({ m: 'POST', p: CHAT_PATH, b: CHAT(), headers: {} });
|
|
846
|
+
return r.status === 401 && errType(r) === 'invalid_authentication_error';
|
|
847
|
+
}),
|
|
848
|
+
),
|
|
849
|
+
done('moonshot.auth.invalid_key', 'auth', 'Auth: the reserved invalid sentinel → 401 incorrect_api_key_error', 'api', 'core', () =>
|
|
850
|
+
withRootH(async (h) => {
|
|
851
|
+
const r = await h({ m: 'POST', p: CHAT_PATH, b: CHAT(), headers: { authorization: 'Bearer sk_invalid' } });
|
|
852
|
+
return r.status === 401 && errType(r) === 'incorrect_api_key_error';
|
|
853
|
+
}),
|
|
854
|
+
),
|
|
855
|
+
|
|
856
|
+
// ── Errors (the fidelity surface) ──────────────────────────────────────────────────────
|
|
857
|
+
done('moonshot.errors.envelope_keys', 'errors', 'Errors: every /v1 error carries message+type and ONLY keys Moonshot\'s ErrorResponse declares (message, type, code)', 'api', 'core', () =>
|
|
858
|
+
withRoot(async (h) => {
|
|
859
|
+
const DECLARED = new Set(['message', 'type', 'code']);
|
|
860
|
+
const cases = [
|
|
861
|
+
await h({ m: 'POST', p: CHAT_PATH, b: { messages: [] } }),
|
|
862
|
+
await h({ m: 'GET', p: `${MOONSHOT_API_PREFIX}/models/nope` }),
|
|
863
|
+
await h({ m: 'GET', p: `${MOONSHOT_API_PREFIX}/files/file_nope` }),
|
|
864
|
+
await h({ m: 'POST', p: `${MOONSHOT_API_PREFIX}/batches`, b: { input_file_id: 'file_nope', endpoint: '/v1/chat/completions', completion_window: '24h' } }),
|
|
865
|
+
];
|
|
866
|
+
return cases.every((r) => {
|
|
867
|
+
if (r.status < 400) return false;
|
|
868
|
+
const e = (r.body as Body)?.error;
|
|
869
|
+
if (typeof e?.message !== 'string' || !e.message || typeof e?.type !== 'string') return false;
|
|
870
|
+
return Object.keys(e).every((k) => DECLARED.has(k));
|
|
871
|
+
});
|
|
872
|
+
}),
|
|
873
|
+
),
|
|
874
|
+
done('moonshot.errors.unknown_route', 'errors', 'Errors: an unmodeled route answers 404 with the vendor message shape — resource_not_found_error under /v1, not_found_error in the Anthropic envelope under /anthropic', 'api', 'core', () =>
|
|
875
|
+
withRoot(async (h) => {
|
|
876
|
+
const r = await h({ m: 'GET', p: `${MOONSHOT_API_PREFIX}/fine_tuning/jobs` });
|
|
877
|
+
const post = await h({ m: 'POST', p: `${MOONSHOT_API_PREFIX}/embeddings`, b: { input: 'x', model: 'kimi-k3' } });
|
|
878
|
+
if (!(r.status === 404 && errType(r) === 'resource_not_found_error'
|
|
879
|
+
&& String((r.body as Body).error.message).includes('Unknown request URL')
|
|
880
|
+
&& post.status === 404)) return false;
|
|
881
|
+
// The /anthropic unknown route answers the ANTHROPIC envelope (the round-two router fix
|
|
882
|
+
// had no verify): a not_found_error inside {type:error, error:{…}}, never the /v1 shape.
|
|
883
|
+
const anth = await h({ m: 'POST', p: `${MESSAGES_PREFIX}/nope`, b: { model: 'kimi-k3' } });
|
|
884
|
+
const ab = anth.body as Body;
|
|
885
|
+
return anth.status === 404 && ab.type === 'error' && ab.error?.type === 'not_found_error'
|
|
886
|
+
&& typeof ab.error?.message === 'string' && !('code' in (ab.error ?? {}));
|
|
887
|
+
}),
|
|
888
|
+
),
|
|
889
|
+
done('moonshot.errors.rate_limit_429', 'errors', 'Errors: the deterministic rate-limit trigger answers 429 rate_limit_reached_error with the documented X-RateLimit-* header family (the tier table names those three; retry-after is the twin\'s own addition) — and the /anthropic 429 carries the SAME headers in the Anthropic envelope', 'api', 'core', () =>
|
|
890
|
+
withRootH(async (h) => {
|
|
891
|
+
const r = await h({ m: 'POST', p: CHAT_PATH, b: CHAT(), headers: { authorization: 'Bearer sk-twin', 'x-twin-force-rate-limit': '1' } });
|
|
892
|
+
if (r.status !== 429 || errType(r) !== 'rate_limit_reached_error') return false;
|
|
893
|
+
const hdrs = r.headers ?? {};
|
|
894
|
+
if (!(hdrs['retry-after'] === '20' && hdrs['x-ratelimit-limit'] === '3'
|
|
895
|
+
&& hdrs['x-ratelimit-remaining'] === '0' && typeof hdrs['x-ratelimit-reset'] === 'string')) return false;
|
|
896
|
+
// The re-enveloped /anthropic 429 keeps the headers (dropping them left the documented
|
|
897
|
+
// SDK blind to the back-off — the round-three finding).
|
|
898
|
+
const anth = await h({ m: 'POST', p: MESSAGES_PATH, b: { model: 'kimi-k3', messages: [{ role: 'user', content: 'x' }], max_tokens: 10 }, headers: { 'x-api-key': 'sk-twin', 'x-twin-force-rate-limit': '1' } });
|
|
899
|
+
const ahdrs = anth.headers ?? {};
|
|
900
|
+
return anth.status === 429 && (anth.body as Body).error?.type === 'rate_limit_error'
|
|
901
|
+
&& ahdrs['retry-after'] === '20' && ahdrs['x-ratelimit-limit'] === '3';
|
|
902
|
+
}),
|
|
903
|
+
),
|
|
904
|
+
done('moonshot.errors.server_unavailable_503', 'errors', 'Errors: the deterministic outage trigger answers 503 server_unavailable (Moonshot\'s documented type)', 'api', 'common', () =>
|
|
905
|
+
withRootH(async (h) => {
|
|
906
|
+
const r = await h({ m: 'POST', p: CHAT_PATH, b: CHAT(), headers: { authorization: 'Bearer sk-twin', 'x-twin-force-server-unavailable': '1' } });
|
|
907
|
+
return r.status === 503 && errType(r) === 'server_unavailable';
|
|
908
|
+
}),
|
|
909
|
+
),
|
|
910
|
+
done('moonshot.errors.read_only_405', 'errors', 'Errors: a read-only twin refuses every mutation with a vendor-shaped 405', 'api', 'common', () =>
|
|
911
|
+
withRoot(async (h) => {
|
|
912
|
+
const root = mkdtempSync(join(tmpdir(), 'moonshot-cap-ro-'));
|
|
913
|
+
try {
|
|
914
|
+
const r = await handleMoonshotTwinRequest({ method: 'POST', path: CHAT_PATH, body: JSON.stringify(CHAT()), root, readOnly: true });
|
|
915
|
+
return r.status === 405 && errType(r) === 'invalid_request_error';
|
|
916
|
+
} finally {
|
|
917
|
+
rmSync(root, { recursive: true, force: true });
|
|
918
|
+
}
|
|
919
|
+
}),
|
|
920
|
+
),
|
|
921
|
+
|
|
922
|
+
// ── Rate budget (the fail-closed client-side guard) ────────────────────────────────────
|
|
923
|
+
done('moonshot.budget.declaration', 'rate_limits', 'Rate budget: the declaration is armed at module load, prices inference/web-search above reads, and nothing is free', 'connector', 'common', () =>
|
|
924
|
+
verifyBoundary('moonshot.budget.declaration', () => {
|
|
925
|
+
// The LITERALS, not the module's own constants: comparing moonshotCallWeight against
|
|
926
|
+
// MOONSHOT_CALL_WEIGHTS.inference was a tautology (lowering the constant left it green —
|
|
927
|
+
// the round-two finding). The rule is the grounding, so the rule is asserted: inference
|
|
928
|
+
// and web-search cost 6, reads cost 2, and inference is strictly ABOVE a read.
|
|
929
|
+
if (moonshotCallWeight('POST', CHAT_PATH) !== 6) return false;
|
|
930
|
+
if (moonshotCallWeight('POST', `${MOONSHOT_API_PREFIX}/responses`) !== 6) return false;
|
|
931
|
+
if (moonshotCallWeight('POST', MESSAGES_PATH) !== 6) return false;
|
|
932
|
+
if (moonshotCallWeight('POST', `${MOONSHOT_API_PREFIX}/tools/search_pro`) !== 6) return false;
|
|
933
|
+
if (moonshotCallWeight('GET', `${MOONSHOT_API_PREFIX}/models`) !== 2) return false;
|
|
934
|
+
if (moonshotCallWeight('POST', `${MOONSHOT_API_PREFIX}/tokenizers/estimate-token-count`) !== 2) return false;
|
|
935
|
+
if (!(MOONSHOT_CALL_WEIGHTS.inference > MOONSHOT_CALL_WEIGHTS.other)) return false;
|
|
936
|
+
return MOONSHOT_BUDGET_CEILING > 0;
|
|
937
|
+
}),
|
|
938
|
+
// MUTATION-SWEEP NOTE: this cell reads hollow BY METHOD, not by fact — its lines live in
|
|
939
|
+
// moonshot-budget.ts, outside the sweep's default file set. Hand-walked (round-four review):
|
|
940
|
+
// deleting the `^POST /anthropic/v1/messages$` rule reddens this cell by name; changing the
|
|
941
|
+
// inference weight 6 → 3 reddens this cell by name; the module loads either way. The ceiling
|
|
942
|
+
// VALUE (60) is pinned only as > 0 here and by the arithmetic in
|
|
943
|
+
// moonshot.budget.throws_at_ceiling; a 10× ceiling change leaves both green — a known weak
|
|
944
|
+
// pin, not a hollow one.
|
|
945
|
+
),
|
|
946
|
+
done('moonshot.budget.throws_at_ceiling', 'rate_limits', 'Rate budget: charging up to the ceiling is admitted and the call that would EXCEED it throws kind \'ceiling\' naming the spend — the ledger\'s persistence to the key-scoped path is proven in moonshot-budget.test.ts ("Persisted, not remembered")', 'connector', 'common', () =>
|
|
947
|
+
verifyBoundary('moonshot.budget.throws_at_ceiling', () => {
|
|
948
|
+
const dir = mkdtempSync(join(tmpdir(), 'moonshot-cap-budget-'));
|
|
949
|
+
try {
|
|
950
|
+
// A REAL millisecond epoch clock (a 1_000_000 `now` threw 'clock-invalid' on the FIRST
|
|
951
|
+
// checkBudget, so the bare catch reported the guard green while never once exercising
|
|
952
|
+
// the ceiling — the round-three tautology).
|
|
953
|
+
let t = 1_800_000_000_000;
|
|
954
|
+
const budget = new MoonshotBudget({ path: join(dir, 'ledger.json'), now: () => t });
|
|
955
|
+
const fits = Math.floor(MOONSHOT_BUDGET_CEILING / MOONSHOT_CALL_WEIGHTS.inference); // 10
|
|
956
|
+
for (let i = 0; i < fits; i++) budget.checkBudget(MOONSHOT_CALL_WEIGHTS.inference);
|
|
957
|
+
// The call that would EXCEED the ceiling is the one that throws — not one before it.
|
|
958
|
+
let threw: InstanceType<typeof MoonshotBudgetError> | null = null;
|
|
959
|
+
try {
|
|
960
|
+
budget.checkBudget(MOONSHOT_CALL_WEIGHTS.inference);
|
|
961
|
+
} catch (e) {
|
|
962
|
+
if (e instanceof MoonshotBudgetError) threw = e;
|
|
963
|
+
else throw e;
|
|
964
|
+
}
|
|
965
|
+
if (!threw) return false;
|
|
966
|
+
if (threw.kind !== 'ceiling' || threw.vendor !== 'moonshot') return false;
|
|
967
|
+
// The refusal names the arithmetic: spend, ceiling, weight.
|
|
968
|
+
if (!threw.message.includes(`${MOONSHOT_BUDGET_CEILING}`) || !threw.message.includes(`${MOONSHOT_CALL_WEIGHTS.inference}`)) return false;
|
|
969
|
+
// The window still rolls: aging the spend out restores the allowance.
|
|
970
|
+
t += MOONSHOT_RATE_BUDGET_WINDOW_MS + 1;
|
|
971
|
+
budget.checkBudget(MOONSHOT_CALL_WEIGHTS.inference);
|
|
972
|
+
return true;
|
|
973
|
+
} finally {
|
|
974
|
+
rmSync(dir, { recursive: true, force: true });
|
|
975
|
+
}
|
|
976
|
+
}),
|
|
977
|
+
),
|
|
978
|
+
|
|
979
|
+
// ── Scenario scripting (twin-only scaffolding, NOT vendor surface) is deliberately ABSENT ──
|
|
980
|
+
// from the capability manifest (ADDING_A_TWIN.md: scenario support is kept OUT — it is not
|
|
981
|
+
// vendor surface) and is gated by src/moonshot-scenario.test.ts instead (the deepinfra/deepseek
|
|
982
|
+
// precedent).
|
|
983
|
+
|
|
984
|
+
// ── Connector (the live-vendor boundary) ───────────────────────────────────────────────
|
|
985
|
+
done('moonshot.connector.pull', 'connector', 'Connector: pull real Models / Files / Batches / Balance via the injected executor', 'connector', 'core', () =>
|
|
986
|
+
withConnectorRoot('moonshot.connector.pull', async () => {
|
|
987
|
+
const { execute, calls } = fakeExecute((m, p) => {
|
|
988
|
+
if (p === '/v1/models') return { data: [{ id: 'kimi-k3', object: 'model', created: 42, owned_by: 'moonshot' }] };
|
|
989
|
+
if (p === '/v1/files') return { data: [{ id: 'file_real_1', object: 'file', bytes: 3, created_at: 1, filename: 'real.jsonl', purpose: 'batch', status: 'ready' }] };
|
|
990
|
+
if (p === '/v1/batches') return { data: [] };
|
|
991
|
+
if (p === '/v1/users/me/balance') return { code: 0, data: { available_balance: 12.5, voucher_balance: 2, cash_balance: 10.5 }, scode: '0x0', status: true };
|
|
992
|
+
return { data: [] };
|
|
993
|
+
});
|
|
994
|
+
const resources = await pullMoonshotState(execute);
|
|
995
|
+
// Every modeled collection was REALLY swept — recorded calls, not a return-value tautology.
|
|
996
|
+
for (const p of ['/v1/models', '/v1/files', '/v1/batches', '/v1/users/me/balance']) {
|
|
997
|
+
if (!calls.includes(`GET ${p}`)) return false;
|
|
998
|
+
}
|
|
999
|
+
const types = new Map(resources.map((r) => [r.type, r]));
|
|
1000
|
+
return types.get('model')?.id === 'kimi-k3'
|
|
1001
|
+
&& types.get('file')?.id === 'file_real_1'
|
|
1002
|
+
&& types.get('balance')?.id === 'me'
|
|
1003
|
+
&& (types.get('balance')?.fields as Body)?.available_balance === 12.5;
|
|
1004
|
+
}),
|
|
1005
|
+
),
|
|
1006
|
+
done('moonshot.connector.pull_refuses_an_error_envelope', 'connector', 'Connector: a refused pull is NOT an empty account — an error envelope throws instead of folding empty state', 'connector', 'core', () =>
|
|
1007
|
+
withConnectorRoot('moonshot.connector.pull_refuses_an_error_envelope', async () => {
|
|
1008
|
+
const { execute } = fakeExecute(() => ({ error: { message: 'Invalid API key', type: 'invalid_authentication_error' } }));
|
|
1009
|
+
let threw = '';
|
|
1010
|
+
try { await pullMoonshotState(execute); } catch (e) { threw = String((e as Error).message); }
|
|
1011
|
+
return threw.includes('Invalid API key');
|
|
1012
|
+
}),
|
|
1013
|
+
),
|
|
1014
|
+
done('moonshot.connector.pull_folds_into_the_log', 'connector', 'Connector: pulled state folds into the event log and is SERVED by the API (shadow-diff makes a re-pull a no-op)', 'connector', 'core', () =>
|
|
1015
|
+
withConnectorRoot('moonshot.connector.pull_folds_into_the_log', async (root) => {
|
|
1016
|
+
const before = await handleMoonshotTwinRequest({ method: 'GET', path: `${MOONSHOT_API_PREFIX}/files`, root });
|
|
1017
|
+
if (((before.body as Body).data as Body[]).some((f) => f.id === 'file_real_1')) return false;
|
|
1018
|
+
const { execute } = fakeExecute((m, p) => {
|
|
1019
|
+
if (p === '/v1/files') return { data: [{ id: 'file_real_1', object: 'file', bytes: 3, created_at: 1, filename: 'real.jsonl', purpose: 'batch', status: 'ready' }] };
|
|
1020
|
+
if (p === '/v1/users/me/balance') return { code: 0, data: { available_balance: 12.5, voucher_balance: 2, cash_balance: 10.5 }, scode: '0x0', status: true };
|
|
1021
|
+
return { data: [] };
|
|
1022
|
+
});
|
|
1023
|
+
const first = await syncMoonshotFromReal(execute, { root, occurredAt: '2026-06-15T00:00:00Z' });
|
|
1024
|
+
if (first.observed < 2 || first.deltasAppended < 2) return false;
|
|
1025
|
+
const after = await handleMoonshotTwinRequest({ method: 'GET', path: `${MOONSHOT_API_PREFIX}/files`, root });
|
|
1026
|
+
const row = ((after.body as Body).data as Body[]).find((f) => f.id === 'file_real_1');
|
|
1027
|
+
if (!row || row.filename !== 'real.jsonl') return false;
|
|
1028
|
+
// The balance row is SERVED too — the pulled figure replaces the stub.
|
|
1029
|
+
const bal = await handleMoonshotTwinRequest({ method: 'GET', path: `${MOONSHOT_API_PREFIX}/users/me/balance`, root });
|
|
1030
|
+
if ((bal.body as Body).data?.available_balance !== 12.5) return false;
|
|
1031
|
+
const again = await syncMoonshotFromReal(execute, { root, occurredAt: '2026-06-15T00:01:00Z' });
|
|
1032
|
+
return again.deltasAppended === 0;
|
|
1033
|
+
}),
|
|
1034
|
+
),
|
|
1035
|
+
done('moonshot.connector.push', 'connector', 'Connector: a pending batch create pushes the right method, path AND payload, and is confirmed exactly once', 'connector', 'core', () =>
|
|
1036
|
+
withConnectorRoot('moonshot.connector.push', async (root) => {
|
|
1037
|
+
const f = await handleMoonshotTwinRequest({ method: 'POST', path: `${MOONSHOT_API_PREFIX}/files`, body: JSON.stringify({ purpose: 'batch', filename: 'local.jsonl', content: 'x' }), root, occurredAt: '2026-06-15T00:00:00Z' });
|
|
1038
|
+
const b = await handleMoonshotTwinRequest({ method: 'POST', path: `${MOONSHOT_API_PREFIX}/batches`, body: JSON.stringify({ input_file_id: id(f), endpoint: '/v1/chat/completions', completion_window: '48h', metadata: { run: 'nightly' } }), root, occurredAt: '2026-06-15T00:00:01Z' });
|
|
1039
|
+
if (!ok(f) || !ok(b)) return false;
|
|
1040
|
+
const pending = pendingActions('moonshot', root).filter((a) => a.subject.type === 'batch');
|
|
1041
|
+
if (pending.length !== 1) return false;
|
|
1042
|
+
const { execute, calls, bodies } = fakeExecute(() => ({ id: 'batch_real_pushed' }));
|
|
1043
|
+
const res = await pushPendingMoonshotActions(execute, { root, occurredAt: '2026-06-15T00:00:02Z' });
|
|
1044
|
+
const batchCall = calls.indexOf('POST /v1/batches');
|
|
1045
|
+
if (batchCall < 0 || res.pushed < 1) return false;
|
|
1046
|
+
// THE PAYLOAD, not just the path: exactly the keys Moonshot's BatchCreateRequest accepts.
|
|
1047
|
+
const sent = bodies[batchCall] as Body;
|
|
1048
|
+
if (!sent || sent.input_file_id !== id(f) || sent.endpoint !== '/v1/chat/completions') return false;
|
|
1049
|
+
if (sent.completion_window !== '48h' || (sent.metadata as Body)?.run !== 'nightly') return false;
|
|
1050
|
+
if ('status' in sent || 'created_at' in sent || 'expires_at' in sent) return false; // derived fields must not be pushed
|
|
1051
|
+
if (!Object.values(res.externalIds).includes('batch_real_pushed')) return false;
|
|
1052
|
+
if (pendingActions('moonshot', root).some((a) => a.subject.type === 'batch')) return false;
|
|
1053
|
+
// The unpushable file create is REPORTED, not silently dropped, and stays pending.
|
|
1054
|
+
if (!res.refused.some((r) => r.operation === 'file.create' && r.reason.includes('multipart'))) return false;
|
|
1055
|
+
if (!pendingActions('moonshot', root).some((a) => a.subject.type === 'file')) return false;
|
|
1056
|
+
// Idempotency: a re-push enacts nothing.
|
|
1057
|
+
const before = calls.length;
|
|
1058
|
+
const again = await pushPendingMoonshotActions(execute, { root, occurredAt: '2026-06-15T00:00:03Z' });
|
|
1059
|
+
return again.pushed === 0 && calls.length === before;
|
|
1060
|
+
}),
|
|
1061
|
+
),
|
|
1062
|
+
done('moonshot.connector.push_file_create_refused', 'connector', 'Connector: a local file create is REFUSED at push (Moonshot\'s files endpoint is multipart), never faked as JSON', 'connector', 'common', () =>
|
|
1063
|
+
withConnectorRoot('moonshot.connector.push_file_create_refused', async () => {
|
|
1064
|
+
const { execute, calls } = fakeExecute(() => ({ id: 'batch_real' }));
|
|
1065
|
+
// The pushable control FIRST, so a dead connector seam (which makes everything throw)
|
|
1066
|
+
// cannot satisfy this verify with the negative half alone.
|
|
1067
|
+
const good = await pushMoonshotAction(execute, { operation: 'batch.create', subject: { type: 'batch', id: 'batch_twin_1' }, fields: { input_file_id: 'file_twin_1', endpoint: '/v1/chat/completions', completion_window: '24h' } });
|
|
1068
|
+
if (good.externalId !== 'batch_real' || calls.length !== 1) return false;
|
|
1069
|
+
let why = '';
|
|
1070
|
+
try {
|
|
1071
|
+
await pushMoonshotAction(execute, { operation: 'file.create', subject: { type: 'file', id: 'file_twin_1' }, fields: { purpose: 'batch', filename: 'a.jsonl' } });
|
|
1072
|
+
} catch (e) { why = String((e as Error).message); }
|
|
1073
|
+
// Refused with a REASON, and the vendor was never touched.
|
|
1074
|
+
return why.includes('multipart') && calls.length === 1;
|
|
1075
|
+
}),
|
|
1076
|
+
),
|
|
1077
|
+
done('moonshot.connector.push_refuses_unsupported_op', 'connector', 'Connector: an unpushable operation throws instead of being silently dropped', 'connector', 'common', () =>
|
|
1078
|
+
withConnectorRoot('moonshot.connector.push_refuses_unsupported_op', async () => {
|
|
1079
|
+
const { execute, calls } = fakeExecute(() => ({ id: 'batch_real' }));
|
|
1080
|
+
// The SUPPORTED op must succeed here too, so a dead connector seam (which would make BOTH
|
|
1081
|
+
// calls throw) cannot pass this verify by satisfying only the negative half.
|
|
1082
|
+
const good = await pushMoonshotAction(execute, { operation: 'batch.create', subject: { type: 'batch', id: 'batch_twin_1' }, fields: { input_file_id: 'file_twin_1', endpoint: '/v1/chat/completions', completion_window: '24h' } });
|
|
1083
|
+
if (good.externalId !== 'batch_real' || calls.length !== 1) return false;
|
|
1084
|
+
let threw = false;
|
|
1085
|
+
try {
|
|
1086
|
+
await pushMoonshotAction(execute, { operation: 'batch.update', subject: { type: 'batch', id: 'batch_twin_1' }, fields: {} });
|
|
1087
|
+
} catch (e) {
|
|
1088
|
+
threw = String((e as Error).message).includes('unsupported operation');
|
|
1089
|
+
}
|
|
1090
|
+
// The refused op must not have reached the vendor at all.
|
|
1091
|
+
return threw && calls.length === 1;
|
|
1092
|
+
}),
|
|
1093
|
+
),
|
|
1094
|
+
done('moonshot.connector.full_sync', 'connector', 'Connector: full sync pushes pending writes then pulls all collections, idempotently', 'connector', 'core', () =>
|
|
1095
|
+
withConnectorRoot('moonshot.connector.full_sync', async (root) => {
|
|
1096
|
+
// Seed a BATCH create (a JSON endpoint the connector can genuinely push); a local FILE
|
|
1097
|
+
// create is deliberately unpushable (moonshot.connector.push_file_create_refused).
|
|
1098
|
+
const f = await handleMoonshotTwinRequest({ method: 'POST', path: `${MOONSHOT_API_PREFIX}/files`, body: JSON.stringify({ purpose: 'batch', filename: 'local.jsonl', content: 'x' }), root, occurredAt: '2026-06-15T00:00:00Z' });
|
|
1099
|
+
await handleMoonshotTwinRequest({ method: 'POST', path: `${MOONSHOT_API_PREFIX}/batches`, body: JSON.stringify({ input_file_id: id(f), endpoint: '/v1/chat/completions', completion_window: '24h' }), root, occurredAt: '2026-06-15T00:00:01Z' });
|
|
1100
|
+
const { execute, calls } = fakeExecute((m, p) => {
|
|
1101
|
+
if (m === 'GET' && p === '/v1/files') return { data: [{ id: 'file_real_1', object: 'file', bytes: 1, created_at: 1, filename: 'real.jsonl', purpose: 'batch', status: 'ready' }] };
|
|
1102
|
+
if (m === 'GET' && p === '/v1/users/me/balance') return { code: 0, data: { available_balance: 1, voucher_balance: 0, cash_balance: 1 }, scode: '0x0', status: true };
|
|
1103
|
+
if (m === 'GET') return { data: [] };
|
|
1104
|
+
return { id: 'batch_real_pushed' };
|
|
1105
|
+
});
|
|
1106
|
+
const res = await fullSyncMoonshot(execute, { root, occurredAt: '2026-06-15T00:00:02Z' });
|
|
1107
|
+
if (res.pushed !== 1 || res.deltasAppended < 1) return false;
|
|
1108
|
+
// Assert the collections were REALLY swept, by the calls the fake recorded — `res.collections`
|
|
1109
|
+
// alone is COLLECTIONS.length returned by the function under test, i.e. a tautology (§9 NIT).
|
|
1110
|
+
for (const p of ['/v1/models', '/v1/files', '/v1/batches', '/v1/users/me/balance']) {
|
|
1111
|
+
if (!calls.includes(`GET ${p}`)) return false;
|
|
1112
|
+
}
|
|
1113
|
+
// The unpushable file create stays pending by design; the batch create is gone.
|
|
1114
|
+
if (pendingActions('moonshot', root).some((a) => a.subject.type === 'batch')) return false;
|
|
1115
|
+
const again = await fullSyncMoonshot(execute, { root, occurredAt: '2026-06-15T00:00:03Z' });
|
|
1116
|
+
return again.pushed === 0 && again.deltasAppended === 0;
|
|
1117
|
+
}),
|
|
1118
|
+
),
|
|
1119
|
+
done('moonshot.connector.push_request_shape', 'connector', "Connector: ops map to the right method+path, addressed by the VENDOR's id — never the twin's own mint", 'connector', 'common', async () => {
|
|
1120
|
+
// The groq pack's §9 ROUND TWO BLOCKER, back-applied: `batch_twin_1` is the TWIN's mint, a
|
|
1121
|
+
// resource the real account has never heard of; a push addressed by it would 404 at the vendor.
|
|
1122
|
+
const create = moonshotRequestForAction({ operation: 'batch.create', subject: { type: 'batch', id: 'batch_twin_1' } });
|
|
1123
|
+
const cancel = moonshotRequestForAction({ operation: 'batch.cancel', subject: { type: 'batch', id: 'batch_twin_1' } }, 'batch_01j_real');
|
|
1124
|
+
const del = moonshotRequestForAction({ operation: 'file.delete', subject: { type: 'file', id: 'file_twin_1' } }, 'file_01j_real');
|
|
1125
|
+
if (create.method !== 'POST' || create.path !== '/v1/batches') return false;
|
|
1126
|
+
if (cancel.method !== 'POST' || cancel.path !== '/v1/batches/batch_01j_real/cancel') return false;
|
|
1127
|
+
if (del.method !== 'DELETE' || del.path !== '/v1/files/file_01j_real') return false;
|
|
1128
|
+
// A local mint, unresolved, must THROW rather than address the real account.
|
|
1129
|
+
let refusedLocal = '';
|
|
1130
|
+
try { moonshotRequestForAction({ operation: 'batch.cancel', subject: { type: 'batch', id: 'batch_twin_1' } }); } catch (e) { refusedLocal = String((e as Error).message); }
|
|
1131
|
+
let unknownType = false;
|
|
1132
|
+
try { moonshotRequestForAction({ operation: 'model.create', subject: { type: 'model', id: 'm1' } }); } catch { unknownType = true; }
|
|
1133
|
+
return refusedLocal.includes("twin's own id") && unknownType;
|
|
1134
|
+
}),
|
|
1135
|
+
done('moonshot.connector.push_addresses_the_vendor_id', 'connector', "Connector: a pushed create records the vendor's id, and a later cancel/delete uses IT, not the local mint", 'connector', 'core', () =>
|
|
1136
|
+
withConnectorRoot('moonshot.connector.push_addresses_the_vendor_id', async (root) => {
|
|
1137
|
+
const f = await handleMoonshotTwinRequest({ method: 'POST', path: `${MOONSHOT_API_PREFIX}/files`, body: JSON.stringify({ purpose: 'batch', filename: 'in.jsonl', content: 'x' }), root, occurredAt: '2026-06-15T00:00:00Z' });
|
|
1138
|
+
const b = await handleMoonshotTwinRequest({ method: 'POST', path: `${MOONSHOT_API_PREFIX}/batches`, body: JSON.stringify({ input_file_id: id(f), endpoint: '/v1/chat/completions', completion_window: '24h' }), root, occurredAt: '2026-06-15T00:00:01Z' });
|
|
1139
|
+
if (!ok(f) || !ok(b) || !String(id(b)).includes('_twin_')) return false;
|
|
1140
|
+
const { execute, calls } = fakeExecute(() => ({ id: 'batch_01jREAL' }));
|
|
1141
|
+
await pushPendingMoonshotActions(execute, { root, occurredAt: '2026-06-15T00:00:02Z' });
|
|
1142
|
+
if (!calls.includes('POST /v1/batches')) return false;
|
|
1143
|
+
// Now cancel LOCALLY and push again: the request must name the VENDOR's id.
|
|
1144
|
+
await handleMoonshotTwinRequest({ method: 'POST', path: `${MOONSHOT_API_PREFIX}/batches/${id(b)}/cancel`, root, occurredAt: '2026-06-15T00:00:03Z' });
|
|
1145
|
+
const res = await pushPendingMoonshotActions(execute, { root, occurredAt: '2026-06-15T00:00:04Z' });
|
|
1146
|
+
const cancelCall = calls.find((c) => c.endsWith('/cancel'));
|
|
1147
|
+
if (res.pushed !== 1 || cancelCall !== 'POST /v1/batches/batch_01jREAL/cancel') return false;
|
|
1148
|
+
// …and nothing ever addressed the real account by the twin's own mint.
|
|
1149
|
+
if (calls.some((c) => c.includes('_twin_'))) return false;
|
|
1150
|
+
// A subject the vendor never received (the unpushable file) is REFUSED, not guessed at.
|
|
1151
|
+
await handleMoonshotTwinRequest({ method: 'DELETE', path: `${MOONSHOT_API_PREFIX}/files/${id(f)}`, root, occurredAt: '2026-06-15T00:00:05Z' });
|
|
1152
|
+
const after = await pushPendingMoonshotActions(execute, { root, occurredAt: '2026-06-15T00:00:06Z' });
|
|
1153
|
+
return after.pushed === 0 && after.refused.some((r) => r.reason.includes('no vendor id recorded'));
|
|
1154
|
+
}),
|
|
1155
|
+
),
|
|
1156
|
+
done('moonshot.connector.live_client_refuses_unmodeled_paths', 'connector', 'Connector: the live executor refuses a non-Moonshot path BEFORE any request goes out (the budget guard on modeled calls is proven in moonshot-budget.test.ts)', 'connector', 'common', async () => {
|
|
1157
|
+
const calls: string[] = [];
|
|
1158
|
+
const fetchImpl = (async (url: string) => { calls.push(String(url)); return new Response('{"data":[]}', { status: 200 }); }) as unknown as typeof fetch;
|
|
1159
|
+
const dir = mkdtempSync(join(tmpdir(), 'moonshot-cap-budget-'));
|
|
1160
|
+
try {
|
|
1161
|
+
// The ledger is INJECTED so this never spends against the operator's real ~/.volter/moonshot file.
|
|
1162
|
+
const { liveMoonshotExecute } = await import('./moonshot-connector.ts');
|
|
1163
|
+
const execute = liveMoonshotExecute('sk-fake-never-real', 'https://api.moonshot.test', { fetchImpl, budgetOptions: { path: join(dir, 'ledger.json') } });
|
|
1164
|
+
let threw = false;
|
|
1165
|
+
try { await execute('GET', '/v2/other'); } catch (e) { threw = String((e as Error).message).includes('unmodeled'); }
|
|
1166
|
+
const beforeAllowed = calls.length;
|
|
1167
|
+
if (!threw || beforeAllowed !== 0) return false;
|
|
1168
|
+
await execute('GET', '/v1/models');
|
|
1169
|
+
// The refusal cost the vendor NOTHING, and the modeled path really did go out.
|
|
1170
|
+
return calls.length === beforeAllowed + 1 && calls[0] === 'https://api.moonshot.test/v1/models';
|
|
1171
|
+
} finally {
|
|
1172
|
+
rmSync(dir, { recursive: true, force: true });
|
|
1173
|
+
}
|
|
1174
|
+
}),
|
|
1175
|
+
done('moonshot.connector.pulled_models_are_served', 'connector', 'Connector: models observed by a pull are SERVED by GET /models, not just folded into the log', 'connector', 'common', () =>
|
|
1176
|
+
withConnectorRoot('moonshot.connector.pulled_models_are_served', async (root) => {
|
|
1177
|
+
const before = await handleMoonshotTwinRequest({ method: 'GET', path: `${MOONSHOT_API_PREFIX}/models`, root });
|
|
1178
|
+
if (((before.body as Body).data as Body[]).some((m) => m.id === 'kimi-experimental-twin-only')) return false;
|
|
1179
|
+
const { execute } = fakeExecute((m, p) => p === '/v1/models'
|
|
1180
|
+
? { data: [{ id: 'kimi-experimental-twin-only', object: 'model', created: 42, owned_by: 'moonshot' }] }
|
|
1181
|
+
: p === '/v1/users/me/balance'
|
|
1182
|
+
? { code: 0, data: { available_balance: 1, voucher_balance: 0, cash_balance: 1 }, scode: '0x0', status: true }
|
|
1183
|
+
: { data: [] });
|
|
1184
|
+
const res = await syncMoonshotFromReal(execute, { root, occurredAt: '2026-06-15T00:00:00Z' });
|
|
1185
|
+
if (res.deltasAppended < 1) return false;
|
|
1186
|
+
const after = await handleMoonshotTwinRequest({ method: 'GET', path: `${MOONSHOT_API_PREFIX}/models`, root });
|
|
1187
|
+
const row = ((after.body as Body).data as Body[]).find((m) => m.id === 'kimi-experimental-twin-only');
|
|
1188
|
+
if (!row || row.owned_by !== 'moonshot') return false;
|
|
1189
|
+
// …without duplicating or shadowing a static catalog row. (The by-id retrieve assertion is
|
|
1190
|
+
// gone: Moonshot's OpenAPI declares no /v1/models/{model} operation — moonshot.models.retrieve
|
|
1191
|
+
// records the gap, and the twin 404s the route like the vendor.)
|
|
1192
|
+
const ids = ((after.body as Body).data as Body[]).map((m) => m.id);
|
|
1193
|
+
return new Set(ids).size === ids.length
|
|
1194
|
+
&& ((after.body as Body).data as Body[]).find((m) => m.id === 'kimi-k3')?.owned_by === 'moonshot';
|
|
1195
|
+
}),
|
|
1196
|
+
),
|
|
1197
|
+
|
|
1198
|
+
// ── Conformance harness ────────────────────────────────────────────────────────────────
|
|
1199
|
+
done('moonshot.conformance.probes', 'conformance', 'Offline conformance harness passes (one real probe per claimed endpoint + the router census + both error envelopes + the three streaming grammars)', 'api', 'core', async () => {
|
|
1200
|
+
const { checkMoonshotConformance } = await import('./moonshot-conformance.ts');
|
|
1201
|
+
const report = await checkMoonshotConformance();
|
|
1202
|
+
return report.ok && report.probes >= 19 && report.checksRun >= 30;
|
|
1203
|
+
}),
|
|
1204
|
+
];
|
|
1205
|
+
|
|
1206
|
+
// TWIN-87 committed area census — Moonshot's top-level API product areas (docs nav / the
|
|
1207
|
+
// OpenAPI's tag structure), authored top-down independent of what a manifest entry happens to
|
|
1208
|
+
// already exist for. moonshot-capabilities.test.ts's area-census meta-test (assertAreaCensus)
|
|
1209
|
+
// fails the gate if a declared area has zero manifest entries and no named exclusion, OR if a
|
|
1210
|
+
// manifest entry's `area` drifts outside this list — so a whole missing area can never hide
|
|
1211
|
+
// invisibly.
|
|
1212
|
+
export const MOONSHOT_AREAS = [
|
|
1213
|
+
'auth', 'balance', 'batches', 'chat', 'conformance', 'connector', 'errors', 'files',
|
|
1214
|
+
'messages', 'models', 'rate_limits', 'responses', 'signatures', 'streaming',
|
|
1215
|
+
'tokens', 'tools',
|
|
1216
|
+
] as const;
|
|
1217
|
+
|
|
1218
|
+
export function moonshotCapabilities(): Promise<CapabilityReport> {
|
|
1219
|
+
return checkCapabilities('moonshot', MOONSHOT_CAPABILITIES);
|
|
1220
|
+
}
|