@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,251 @@
|
|
|
1
|
+
/** A chat message param as the caller sends it (Message schema). Content is a string OR a
|
|
2
|
+
* content-part array (text / image_url / video_url — Moonshot's multimodal form). */
|
|
3
|
+
export type MoonshotMessageParam = {
|
|
4
|
+
role: 'system' | 'user' | 'assistant' | 'tool';
|
|
5
|
+
content?: string | Array<Record<string, unknown>> | null;
|
|
6
|
+
name?: string;
|
|
7
|
+
/** Partial Mode: set `partial: true` on the last ASSISTANT message to continue it. */
|
|
8
|
+
partial?: boolean;
|
|
9
|
+
tool_calls?: MoonshotToolCall[];
|
|
10
|
+
tool_call_id?: string;
|
|
11
|
+
/** kimi-k3's dynamic tool loading message: role 'system' with `tools` and NO content. */
|
|
12
|
+
tools?: unknown[];
|
|
13
|
+
};
|
|
14
|
+
/** A function tool_call (faithful shape). */
|
|
15
|
+
export type MoonshotToolCall = {
|
|
16
|
+
id: string;
|
|
17
|
+
type: 'function';
|
|
18
|
+
function: {
|
|
19
|
+
name: string;
|
|
20
|
+
arguments: string;
|
|
21
|
+
};
|
|
22
|
+
};
|
|
23
|
+
/** The chat `usage` object: token counts plus Moonshot's automatic context-cache split. */
|
|
24
|
+
export type MoonshotUsage = {
|
|
25
|
+
prompt_tokens: number;
|
|
26
|
+
completion_tokens: number;
|
|
27
|
+
total_tokens: number;
|
|
28
|
+
/** Tokens served from the automatic context cache (>256 prompt tokens to hit). */
|
|
29
|
+
cached_tokens?: number;
|
|
30
|
+
};
|
|
31
|
+
/** The assistant message Moonshot returns. `reasoning_content` is present when thinking mode
|
|
32
|
+
* is enabled (kimi-k3 always; kimi-k2.6/k2.7-code when `thinking.type` is 'enabled'). */
|
|
33
|
+
export type MoonshotAssistantMessage = {
|
|
34
|
+
role: 'assistant';
|
|
35
|
+
content: string | null;
|
|
36
|
+
tool_calls?: MoonshotToolCall[];
|
|
37
|
+
reasoning_content?: string | null;
|
|
38
|
+
};
|
|
39
|
+
export type MoonshotChoice = {
|
|
40
|
+
index: number;
|
|
41
|
+
message: MoonshotAssistantMessage;
|
|
42
|
+
logprobs?: unknown;
|
|
43
|
+
finish_reason: 'stop' | 'length' | 'tool_calls';
|
|
44
|
+
};
|
|
45
|
+
/** The unary chat.completion response envelope (faithful shape). */
|
|
46
|
+
export type MoonshotChatCompletion = {
|
|
47
|
+
id: string;
|
|
48
|
+
object: 'chat.completion';
|
|
49
|
+
created: number;
|
|
50
|
+
model: string;
|
|
51
|
+
choices: MoonshotChoice[];
|
|
52
|
+
usage: MoonshotUsage;
|
|
53
|
+
};
|
|
54
|
+
/** A streaming chat chunk (ChatCompletionChunk schema). */
|
|
55
|
+
export type MoonshotChatChunk = {
|
|
56
|
+
id: string;
|
|
57
|
+
object: 'chat.completion.chunk';
|
|
58
|
+
created: number;
|
|
59
|
+
model: string;
|
|
60
|
+
choices: Array<{
|
|
61
|
+
index: number;
|
|
62
|
+
delta: {
|
|
63
|
+
role?: string;
|
|
64
|
+
content?: string;
|
|
65
|
+
reasoning_content?: string;
|
|
66
|
+
tool_calls?: Array<{
|
|
67
|
+
index?: number;
|
|
68
|
+
id?: string;
|
|
69
|
+
type?: 'function';
|
|
70
|
+
function?: {
|
|
71
|
+
name?: string;
|
|
72
|
+
arguments?: string;
|
|
73
|
+
};
|
|
74
|
+
}>;
|
|
75
|
+
};
|
|
76
|
+
finish_reason: 'stop' | 'length' | 'tool_calls' | null;
|
|
77
|
+
usage: MoonshotUsage | null;
|
|
78
|
+
}>;
|
|
79
|
+
/** The FINAL chunk carries the whole usage object (stream_options.include_usage / Moonshot's
|
|
80
|
+
* documented final-chunk usage); ordinary chunks carry null. */
|
|
81
|
+
usage?: MoonshotUsage | null;
|
|
82
|
+
};
|
|
83
|
+
/** The served model object (OpenAI-shaped; Moonshot's /v1/models rows). */
|
|
84
|
+
export type MoonshotModelObject = {
|
|
85
|
+
id: string;
|
|
86
|
+
object: 'model';
|
|
87
|
+
created: number;
|
|
88
|
+
owned_by: string;
|
|
89
|
+
};
|
|
90
|
+
/** File purposes — a CLOSED documented set (FileObject schema). */
|
|
91
|
+
export declare const FILE_PURPOSES: readonly ["file-extract", "image", "video", "batch"];
|
|
92
|
+
export type MoonshotFilePurpose = (typeof FILE_PURPOSES)[number];
|
|
93
|
+
export type MoonshotFile = {
|
|
94
|
+
id: string;
|
|
95
|
+
object: 'file';
|
|
96
|
+
bytes: number;
|
|
97
|
+
created_at: number;
|
|
98
|
+
filename: string;
|
|
99
|
+
purpose: MoonshotFilePurpose;
|
|
100
|
+
status: string;
|
|
101
|
+
status_details?: string;
|
|
102
|
+
};
|
|
103
|
+
export type MoonshotBatchCounts = {
|
|
104
|
+
completed: number;
|
|
105
|
+
failed: number;
|
|
106
|
+
total: number;
|
|
107
|
+
};
|
|
108
|
+
export type MoonshotBatch = {
|
|
109
|
+
id: string;
|
|
110
|
+
object: 'batch';
|
|
111
|
+
endpoint: string;
|
|
112
|
+
input_file_id: string;
|
|
113
|
+
completion_window: string;
|
|
114
|
+
status: 'validating' | 'failed' | 'in_progress' | 'finalizing' | 'completed' | 'expired' | 'cancelling' | 'cancelled';
|
|
115
|
+
output_file_id: string | null;
|
|
116
|
+
error_file_id: string | null;
|
|
117
|
+
created_at: number;
|
|
118
|
+
in_progress_at: number | null;
|
|
119
|
+
expires_at: number | null;
|
|
120
|
+
finalizing_at: number | null;
|
|
121
|
+
completed_at: number | null;
|
|
122
|
+
failed_at: number | null;
|
|
123
|
+
cancelling_at: number | null;
|
|
124
|
+
cancelled_at: number | null;
|
|
125
|
+
request_counts: MoonshotBatchCounts;
|
|
126
|
+
metadata: Record<string, string> | null;
|
|
127
|
+
};
|
|
128
|
+
/**
|
|
129
|
+
* Moonshot's OpenAI-surface error envelope (ErrorResponse schema): `error.message` REQUIRED,
|
|
130
|
+
* `type` and `code` optional. The `type` values are Moonshot's documented error-code page
|
|
131
|
+
* (platform.kimi.ai/docs/api/errors, read 2026-09-16) — see moonshot-twin.ts for the mapping.
|
|
132
|
+
*/
|
|
133
|
+
export type MoonshotError = {
|
|
134
|
+
error: {
|
|
135
|
+
message: string;
|
|
136
|
+
type?: string;
|
|
137
|
+
code?: string;
|
|
138
|
+
};
|
|
139
|
+
};
|
|
140
|
+
/** A Messages content block the RESPONSE emits, ordered thinking → text → tool_use. */
|
|
141
|
+
export type MoonshotMessagesBlock = {
|
|
142
|
+
type: 'thinking';
|
|
143
|
+
thinking: string;
|
|
144
|
+
signature?: string;
|
|
145
|
+
} | {
|
|
146
|
+
type: 'text';
|
|
147
|
+
text: string;
|
|
148
|
+
} | {
|
|
149
|
+
type: 'tool_use';
|
|
150
|
+
id: string;
|
|
151
|
+
name: string;
|
|
152
|
+
input: Record<string, unknown>;
|
|
153
|
+
};
|
|
154
|
+
export type MoonshotMessagesResponse = {
|
|
155
|
+
id: string;
|
|
156
|
+
type: 'message';
|
|
157
|
+
role: 'assistant';
|
|
158
|
+
model: string;
|
|
159
|
+
content: MoonshotMessagesBlock[];
|
|
160
|
+
stop_reason: 'end_turn' | 'max_tokens' | 'stop_sequence' | 'tool_use' | 'refusal' | null;
|
|
161
|
+
stop_sequence: string | null;
|
|
162
|
+
usage: {
|
|
163
|
+
input_tokens: number;
|
|
164
|
+
output_tokens: number;
|
|
165
|
+
cache_read_input_tokens?: number;
|
|
166
|
+
cache_creation_input_tokens?: number;
|
|
167
|
+
};
|
|
168
|
+
};
|
|
169
|
+
/** A Messages SSE event. `data` is the JSON payload; there is no [DONE] sentinel — the stream
|
|
170
|
+
* ends after `message_stop` (Anthropic's grammar, which this surface follows). */
|
|
171
|
+
export type MessagesSseEvent = {
|
|
172
|
+
event?: string;
|
|
173
|
+
data?: Record<string, unknown>;
|
|
174
|
+
done?: boolean;
|
|
175
|
+
};
|
|
176
|
+
/** An output item, ordered web_search_call → reasoning → message → tool calls. */
|
|
177
|
+
export type MoonshotResponsesOutputItem = {
|
|
178
|
+
type: 'reasoning';
|
|
179
|
+
id: string;
|
|
180
|
+
summary: Array<{
|
|
181
|
+
type: 'summary_text';
|
|
182
|
+
text: string;
|
|
183
|
+
}>;
|
|
184
|
+
status?: 'completed';
|
|
185
|
+
} | {
|
|
186
|
+
type: 'message';
|
|
187
|
+
id: string;
|
|
188
|
+
role: 'assistant';
|
|
189
|
+
status: 'completed';
|
|
190
|
+
content: Array<{
|
|
191
|
+
type: 'output_text';
|
|
192
|
+
text: string;
|
|
193
|
+
annotations: unknown[];
|
|
194
|
+
}>;
|
|
195
|
+
} | {
|
|
196
|
+
type: 'function_call';
|
|
197
|
+
id: string;
|
|
198
|
+
call_id: string;
|
|
199
|
+
name: string;
|
|
200
|
+
arguments: string;
|
|
201
|
+
status?: 'completed';
|
|
202
|
+
} | {
|
|
203
|
+
type: 'custom_tool_call';
|
|
204
|
+
id: string;
|
|
205
|
+
call_id: string;
|
|
206
|
+
name: string;
|
|
207
|
+
input: string;
|
|
208
|
+
} | {
|
|
209
|
+
type: 'web_search_call';
|
|
210
|
+
id: string;
|
|
211
|
+
status: 'completed';
|
|
212
|
+
};
|
|
213
|
+
export type MoonshotResponsesUsage = {
|
|
214
|
+
input_tokens: number;
|
|
215
|
+
input_tokens_details?: {
|
|
216
|
+
cached_tokens: number;
|
|
217
|
+
cache_write_tokens: number;
|
|
218
|
+
};
|
|
219
|
+
output_tokens: number;
|
|
220
|
+
output_tokens_details?: {
|
|
221
|
+
reasoning_tokens: number;
|
|
222
|
+
};
|
|
223
|
+
total_tokens: number;
|
|
224
|
+
};
|
|
225
|
+
export type MoonshotResponsesResponse = {
|
|
226
|
+
id: string;
|
|
227
|
+
object: 'response';
|
|
228
|
+
created_at: number;
|
|
229
|
+
completed_at: number | null;
|
|
230
|
+
status: 'in_progress' | 'completed' | 'incomplete' | 'failed';
|
|
231
|
+
model: string;
|
|
232
|
+
output: MoonshotResponsesOutputItem[];
|
|
233
|
+
usage: MoonshotResponsesUsage | null;
|
|
234
|
+
incomplete_details: {
|
|
235
|
+
reason: 'max_output_tokens' | 'content_filter';
|
|
236
|
+
} | null;
|
|
237
|
+
error: {
|
|
238
|
+
code: string;
|
|
239
|
+
message: string;
|
|
240
|
+
} | null;
|
|
241
|
+
store: false;
|
|
242
|
+
};
|
|
243
|
+
/** A single Server-Sent Event the OpenAI-compatible streaming paths emit. `data` is the JSON
|
|
244
|
+
* payload; `[DONE]` is signalled with `done: true` (no data object). */
|
|
245
|
+
export type SseEvent = {
|
|
246
|
+
data?: Record<string, unknown>;
|
|
247
|
+
done?: boolean;
|
|
248
|
+
};
|
|
249
|
+
/** A sink the streaming path writes events into (an injected collector in tests / a real
|
|
250
|
+
* HTTP SSE writer in the server). NO real sockets or setTimeout in the handler. */
|
|
251
|
+
export type SseSink = (event: SseEvent) => void;
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
// Shared wire-shape types for the Moonshot (Kimi) API surface, transcribed from Moonshot's own
|
|
2
|
+
// OpenAPI 3.1.0 document (https://platform.kimi.ai/docs/openapi.json, read 2026-09-16) — the
|
|
3
|
+
// first-party artifact, not an SDK's internal types (no official Moonshot SDK exists; clients
|
|
4
|
+
// are the standard `openai` / `anthropic` SDKs pointed at Moonshot's base URLs, so THIS document
|
|
5
|
+
// is the only generated contract).
|
|
6
|
+
//
|
|
7
|
+
// MOONSHOT IS NOT OPENAI, and the differences below are load-bearing:
|
|
8
|
+
// • THREE inference protocols share one host: OpenAI-compatible `/v1/chat/completions` +
|
|
9
|
+
// `/v1/responses`, and an Anthropic-compatible `/anthropic/v1/messages` whose envelope,
|
|
10
|
+
// content blocks and SSE event grammar are Anthropic's, not OpenAI's;
|
|
11
|
+
// • chat responses carry `reasoning_content` (thinking mode) — not Groq's `reasoning`, not
|
|
12
|
+
// OpenAI's `refusal`;
|
|
13
|
+
// • `usage` carries `cached_tokens` (Moonshot's automatic context caching);
|
|
14
|
+
// • the error envelope is `{ error: { message, type, code? } }` (ErrorResponse schema);
|
|
15
|
+
// • the Messages surface answers `{ type:'error', error:{type,message}, request_id? }`
|
|
16
|
+
// (MessagesErrorResponse schema) — a DIFFERENT envelope on the same host.
|
|
17
|
+
// ── Files ───────────────────────────────────────────────────────────────────────────────
|
|
18
|
+
/** File purposes — a CLOSED documented set (FileObject schema). */
|
|
19
|
+
export const FILE_PURPOSES = ['file-extract', 'image', 'video', 'batch'];
|
package/package.json
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@volter/twin-moonshot",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Local Moonshot (Kimi) twin — a faithful, stateful local Moonshot Platform API your real `openai` / `anthropic` SDKs (swapped base URL) talk to unmodified. The model is stubbed (deterministic), but the protocol envelope across three inference protocols (OpenAI chat completions + Responses, Anthropic-compatible Messages, with streaming grammars, tool calls and cache-split usage), models, files, batches, balance, token counting and signature verify are vendor-faithful. Built on @volter/world-core.",
|
|
5
|
+
"author": "Volter (https://github.com/volter-ai)",
|
|
6
|
+
"license": "Apache-2.0",
|
|
7
|
+
"files": [
|
|
8
|
+
"src",
|
|
9
|
+
"README.md",
|
|
10
|
+
"LICENSE",
|
|
11
|
+
"!**/*.test.ts",
|
|
12
|
+
"!**/*.test.tsx",
|
|
13
|
+
"dist"
|
|
14
|
+
],
|
|
15
|
+
"repository": {
|
|
16
|
+
"type": "git",
|
|
17
|
+
"url": "git+https://github.com/volter-ai/twin.git",
|
|
18
|
+
"directory": "packages/twin/moonshot"
|
|
19
|
+
},
|
|
20
|
+
"homepage": "https://github.com/volter-ai/twin/tree/main/packages/twin/moonshot#readme",
|
|
21
|
+
"type": "module",
|
|
22
|
+
"exports": {
|
|
23
|
+
".": {
|
|
24
|
+
"types": "./dist/src/index.d.ts",
|
|
25
|
+
"default": "./dist/src/index.js"
|
|
26
|
+
}
|
|
27
|
+
},
|
|
28
|
+
"bin": {
|
|
29
|
+
"world-moonshot": "dist/src/cli.js"
|
|
30
|
+
},
|
|
31
|
+
"scripts": {
|
|
32
|
+
"test": "bun test src/*.test.ts",
|
|
33
|
+
"typecheck": "tsc --noEmit",
|
|
34
|
+
"build": "node ../../../scripts/publish/build.mjs",
|
|
35
|
+
"prepack": "node ../../../scripts/publish/prepare-publish.mjs prepack",
|
|
36
|
+
"postpack": "node ../../../scripts/publish/prepare-publish.mjs postpack"
|
|
37
|
+
},
|
|
38
|
+
"peerDependencies": {
|
|
39
|
+
"@volter/world-core": "2.0.0"
|
|
40
|
+
},
|
|
41
|
+
"devDependencies": {
|
|
42
|
+
"@anthropic-ai/sdk": "^0.69.0",
|
|
43
|
+
"@types/bun": "^1.2.20",
|
|
44
|
+
"@types/node": "^24.0.0",
|
|
45
|
+
"@volter/world-core": "2.0.0",
|
|
46
|
+
"@volter/world-tooling": "0.1.0",
|
|
47
|
+
"openai": "^6.16.0",
|
|
48
|
+
"typescript": "^5.9.0"
|
|
49
|
+
},
|
|
50
|
+
"engines": {
|
|
51
|
+
"node": ">=22.3"
|
|
52
|
+
}
|
|
53
|
+
}
|
package/src/cli.ts
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { hasFlag, optionValue } from '@volter/world-core/args';
|
|
3
|
+
import { createMoonshotTwinServer } from './moonshot-server.ts';
|
|
4
|
+
|
|
5
|
+
const [cmd, ...rest] = process.argv.slice(2);
|
|
6
|
+
|
|
7
|
+
if (cmd === 'serve') {
|
|
8
|
+
const root = optionValue(rest, '--root') || process.env.VOLTER_STATE_DIR;
|
|
9
|
+
const port = Number(optionValue(rest, '--port') || 0) || undefined;
|
|
10
|
+
const readOnly = hasFlag(rest, '--read-only'); // a twin accepts writes unless started read-only
|
|
11
|
+
const scenario = optionValue(rest, '--scenario') || undefined; // scripted completions (JSON file)
|
|
12
|
+
const server = await createMoonshotTwinServer({
|
|
13
|
+
...(root ? { root } : {}), ...(port ? { port } : {}), readOnly, ...(scenario ? { scenarioPath: scenario } : {}),
|
|
14
|
+
});
|
|
15
|
+
process.stdout.write(`moonshot twin (Moonshot Platform API at /v1 and /anthropic/v1; model output is a deterministic stub)${readOnly ? ' [read-only]' : ''}${scenario ? ` [scenario: ${scenario}]` : ''} at http://127.0.0.1:${server.port}\n`);
|
|
16
|
+
} else if (cmd === 'conformance') {
|
|
17
|
+
// Lazy — the conformance module is DEV-ONLY and must not be reachable from the runtime
|
|
18
|
+
// entrypoints (recipe §3/§4, enforced by scripts/architecture.test.ts).
|
|
19
|
+
const { checkMoonshotConformance } = await import('./moonshot-conformance.ts');
|
|
20
|
+
const report = await checkMoonshotConformance({ root: process.env.VOLTER_STATE_DIR });
|
|
21
|
+
console.log(JSON.stringify(report, null, 2));
|
|
22
|
+
process.exit(report.ok ? 0 : 1);
|
|
23
|
+
} else {
|
|
24
|
+
console.log('usage: world-moonshot serve [--port N] [--root DIR] [--read-only] [--scenario FILE] | conformance');
|
|
25
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
// @volter/twin-moonshot — the Moonshot (Kimi) twin (one vendor, one package), built on the shared
|
|
2
|
+
// @volter/world-core kernel. The Moonshot Platform API: a vendor-faithful protocol envelope on
|
|
3
|
+
// THREE inference protocols (OpenAI-compatible /v1/chat/completions + /v1/responses, and the
|
|
4
|
+
// Anthropic-compatible /anthropic/v1/messages), a static models catalog, STATEFUL files +
|
|
5
|
+
// batches + account balance, and the deterministic stateless helpers (token counting, signature
|
|
6
|
+
// verify, web-search tools) — over an event/action log.
|
|
7
|
+
//
|
|
8
|
+
// THE DETERMINISTIC STUB IS THE ANSWER: the twin runs no model, so the three inference endpoints
|
|
9
|
+
// return a DETERMINISTIC STUB completion (clearly labeled), never pretending to be real
|
|
10
|
+
// inference. Everything around them — the wire protocol, the rejections, the stateful objects —
|
|
11
|
+
// is faithful.
|
|
12
|
+
//
|
|
13
|
+
// MOONSHOT SHIPS NO SDK OF ITS OWN: its documented integration path is the standard `openai` /
|
|
14
|
+
// `anthropic` SDKs with a swapped base URL (platform.kimi.ai/docs/overview, read 2026-09-16), so
|
|
15
|
+
// the pack claims no vendor SDK in `adoption.sdks` and the fidelity test drives the real
|
|
16
|
+
// `openai` SDK at the twin. (Conformance + capability tooling live in @volter/world-tooling, a
|
|
17
|
+
// dev dependency — NOT re-exported here, per E2.)
|
|
18
|
+
export { handleMoonshotTwinRequest, streamChat, streamMessages, streamResponses, buildChatCompletion, buildMessagesResponse, buildResponsesResponse, MOONSHOT_API_PREFIX, MESSAGES_PREFIX, messagesSignature } from './moonshot-twin.ts';
|
|
19
|
+
export type { MoonshotRequest, MoonshotResponseEnvelope, MessagesSseSink } from './moonshot-twin.ts';
|
|
20
|
+
export { createMoonshotTwinFetch, createMoonshotTwinServer, type MoonshotTwinFetchOptions } from './moonshot-server.ts';
|
|
21
|
+
export { MOONSHOT_MODELS, findModel, BATCH_MODELS, REASONING_EFFORTS, K26_THINKING_TYPES, K27_THINKING_TYPES } from './moonshot-models.ts';
|
|
22
|
+
export {
|
|
23
|
+
buildChatUsage, contentToText, countPromptTokens, estimateTokens, fnv1a, lastUserText,
|
|
24
|
+
stubAssistantText, stubCachedTokens, stubFetchedMarkdown, stubJsonObject, stubReasoningContent,
|
|
25
|
+
stubSearchResults, stubSignature, stubToolArguments, stubToolCall,
|
|
26
|
+
} from './moonshot-stub.ts';
|
|
27
|
+
export type {
|
|
28
|
+
MoonshotAssistantMessage, MoonshotBatch, MoonshotChatChunk, MoonshotChatCompletion, MoonshotChoice,
|
|
29
|
+
MoonshotError, MoonshotFile, MoonshotMessageParam, MoonshotMessagesBlock, MoonshotMessagesResponse,
|
|
30
|
+
MoonshotModelObject, MoonshotResponsesOutputItem, MoonshotResponsesResponse, MoonshotResponsesUsage,
|
|
31
|
+
MoonshotToolCall, MoonshotUsage, SseEvent, SseSink, MessagesSseEvent,
|
|
32
|
+
} from './moonshot-types.ts';
|
|
33
|
+
export { createMoonshotScenarioEngine, moonshotScenarioAdapter, loadMoonshotScenarioDocument, realizeMoonshotRespond } from './moonshot-scenario.ts';
|
|
34
|
+
export type { MoonshotScenarioEngine, MoonshotScenarioRequest, MoonshotScenarioRespond, ScenarioToolCall, ScriptedResult } from './moonshot-scenario.ts';
|
|
35
|
+
export {
|
|
36
|
+
fullSyncMoonshot,
|
|
37
|
+
externalIdFor,
|
|
38
|
+
liveMoonshotExecute,
|
|
39
|
+
mapBalance,
|
|
40
|
+
mapBatch,
|
|
41
|
+
mapFile,
|
|
42
|
+
mapModel,
|
|
43
|
+
moonshotExecuteOver,
|
|
44
|
+
moonshotRequestForAction,
|
|
45
|
+
performMoonshotAction,
|
|
46
|
+
pullMoonshotState,
|
|
47
|
+
pushMoonshotAction,
|
|
48
|
+
pushPendingMoonshotActions,
|
|
49
|
+
syncMoonshotFromReal,
|
|
50
|
+
syncMoonshotFromRemote,
|
|
51
|
+
unpushableReason,
|
|
52
|
+
} from './moonshot-connector.ts';
|
|
53
|
+
export type { MoonshotExecute, LiveMoonshotOptions } from './moonshot-connector.ts';
|
|
54
|
+
// The client-side rate budget — the fail-closed backstop `liveMoonshotExecute` routes every live
|
|
55
|
+
// request through. The MECHANISM is the kernel's shared, vendor-agnostic `RateBudget`; what lives
|
|
56
|
+
// here is Moonshot's DECLARATION (window/ceiling/per-endpoint weights) plus the vendor-bound
|
|
57
|
+
// bindings. Exported so an operator can inspect spend (`snapshot`) and so a caller can catch
|
|
58
|
+
// `MoonshotBudgetError` by type; there is deliberately no export that disables the guard.
|
|
59
|
+
export {
|
|
60
|
+
MOONSHOT_BUDGET_CEILING,
|
|
61
|
+
MOONSHOT_BUDGET_MAX_RETRY_AFTER_S,
|
|
62
|
+
MOONSHOT_BUDGET_WINDOW_MS,
|
|
63
|
+
MOONSHOT_CALL_WEIGHTS,
|
|
64
|
+
MOONSHOT_RATE_BUDGET,
|
|
65
|
+
MoonshotBudget,
|
|
66
|
+
MoonshotBudgetError,
|
|
67
|
+
moonshotBudgetPath,
|
|
68
|
+
moonshotCallWeight,
|
|
69
|
+
} from './moonshot-budget.ts';
|
|
70
|
+
export type { MoonshotBudgetErrorKind, MoonshotBudgetOptions, MoonshotBudgetReservation, MoonshotBudgetSnapshot } from './moonshot-budget.ts';
|
|
71
|
+
|
|
72
|
+
// Registry descriptor: the pack self-describes so tooling can discover it.
|
|
73
|
+
import { registerPack, type TwinPack } from '@volter/world-core';
|
|
74
|
+
import { MOONSHOT_RATE_BUDGET as RATE_BUDGET } from './moonshot-budget.ts';
|
|
75
|
+
import { performMoonshotAction, syncMoonshotFromRemote } from './moonshot-connector.ts';
|
|
76
|
+
export const pack: TwinPack = {
|
|
77
|
+
vendor: 'moonshot',
|
|
78
|
+
// PROTOCOL 2 (docs/contributing/architecture.md#protocol-2-the-pack-is-a-plugin): the pack is a plugin — its wire, its tree, and its half of
|
|
79
|
+
// the real state system: perform one entry against Moonshot, refresh the root from it.
|
|
80
|
+
protocol: '2',
|
|
81
|
+
refresh: { every: '5m', onDemand: { atMost: '30s' } },
|
|
82
|
+
stateSystem: { perform: performMoonshotAction, refresh: syncMoonshotFromRemote },
|
|
83
|
+
// the round trip: a file — the one thing this API mints an id for and keeps, and a fresh id each
|
|
84
|
+
// time, so it repeats cleanly on a branch. (A batch needs a seeded input file; a completion is
|
|
85
|
+
// answered, not kept.)
|
|
86
|
+
roundTrip: { method: 'POST', path: '/v1/files', body: { purpose: 'batch', filename: 'round-trip.jsonl', content: 'x' }, headers: { authorization: 'Bearer sk-round-trip' } },
|
|
87
|
+
// The SAME object moonshot-budget.ts declares at module load — one source of truth, so
|
|
88
|
+
// registering the pack and importing the connector can never arm two different ceilings.
|
|
89
|
+
rateBudget: RATE_BUDGET,
|
|
90
|
+
transport: 'rest',
|
|
91
|
+
archetype: 'generative',
|
|
92
|
+
bin: 'world-moonshot',
|
|
93
|
+
// R2 adopted-as-debt, legibly: declared types no replay can create, each with its reason.
|
|
94
|
+
resourcesUnreachable: {
|
|
95
|
+
'model': 'the vendor catalog',
|
|
96
|
+
'balance': 'the account ledger the vendor keeps; read-only',
|
|
97
|
+
},
|
|
98
|
+
resources: ['file', 'batch', 'balance', 'model'],
|
|
99
|
+
specSource: 'Moonshot Platform API — enumerated from Moonshot\'s own OpenAPI 3.1.0 document (https://platform.kimi.ai/docs/openapi.json, read 2026-09-16; 19 operations) + platform.kimi.ai/docs/{overview,api-reference,errors,models,pricing/limits,guides/context-caching} (read 2026-09-16). Envelope-faithful across the OpenAI-compatible /v1 and Anthropic-compatible /anthropic/v1 surfaces; model output is a labeled deterministic stub.',
|
|
100
|
+
description: 'Moonshot (Kimi) API twin — faithful protocol envelope across three inference protocols (OpenAI chat completions, Responses, Anthropic-compatible Messages) with streaming grammars, tool calls and cache-split usage; models catalog; stateful files/batches/balance; deterministic token counting, signature verify and web-search tool stubs; Moonshot\'s own documented rejections.',
|
|
101
|
+
// Moonshot serves BOTH surfaces on one host. browserRouting points the browser lane at the
|
|
102
|
+
// OpenAI-compatible prefix, which is the dominant client form.
|
|
103
|
+
browserRouting: { apiPathPrefix: '/v1/', loaderHost: 'https://api.moonshot.ai' },
|
|
104
|
+
// ADOPTION (adding-a-twin.md §3): how an app repo betrays that it talks to Moonshot. Declared
|
|
105
|
+
// HERE, not in world-runtime's central SDK_TWINS/ENV_STEM_VENDORS maps — declaring in both
|
|
106
|
+
// throws. Moonshot ships NO SDK of its own: its documented clients are the standard `openai`
|
|
107
|
+
// and `anthropic` SDKs with a swapped base URL, and BOTH of those npm names belong to their
|
|
108
|
+
// own vendors' packs (openai → @volter/twin-openai, anthropic → @volter/twin-anthropic) — a
|
|
109
|
+
// repo depending on `openai` is an OpenAI repo unless its base URL says otherwise. What
|
|
110
|
+
// betrays Moonshot specifically is the ENV STEM (`MOONSHOT_API_KEY` / `MOONSHOT_BASE_URL`)
|
|
111
|
+
// and the deepseek precedent: no sdk claim, envStems only.
|
|
112
|
+
adoption: {
|
|
113
|
+
// No official Moonshot PyPI client is documented (the docs name the OpenAI Python SDK).
|
|
114
|
+
pypi: [],
|
|
115
|
+
sdks: [], envStems: ['MOONSHOT'],
|
|
116
|
+
},
|
|
117
|
+
// INTERCEPTION: the ONE host both documented clients address (the openai SDK pointed at
|
|
118
|
+
// https://api.moonshot.ai/v1 and the anthropic SDK pointed at https://api.moonshot.ai/anthropic).
|
|
119
|
+
hosts: [{ host: 'api.moonshot.ai' }],
|
|
120
|
+
// No app-read endpoint env, deliberately. Moonshot documents no MOONSHOT_BASE_URL-style env
|
|
121
|
+
// var: the standard SDKs take their base URL as a CONSTRUCTOR option (`new OpenAI({
|
|
122
|
+
// baseURL })`), not from the environment, so an app-read var would cover only env-configured
|
|
123
|
+
// callers while disabling the interception that covers all of them (the deepseek/groq
|
|
124
|
+
// precedent). Emitting both would quietly forgo interception (`init.test.ts`, "the app-read
|
|
125
|
+
// endpoint table cannot go stale behind the injector").
|
|
126
|
+
endpointEnvNone: 'Moonshot ships no SDK of its own; its documented clients (the standard openai/anthropic SDKs) take their base URL as a constructor option and read no MOONSHOT_BASE_URL env, so an app-read var would cover only env-configured callers while disabling the api.moonshot.ai interception that covers all traffic.',
|
|
127
|
+
};
|
|
128
|
+
|
|
129
|
+
registerPack(pack);
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
// The client-side rate budget — the fail-closed backstop `liveMoonshotExecute` routes every live
|
|
2
|
+
// request through. The MECHANISM is the kernel's shared, vendor-agnostic `RateBudget`; what lives
|
|
3
|
+
// here is Moonshot's DECLARATION (window/ceiling/per-endpoint weights) plus the vendor-bound
|
|
4
|
+
// bindings, exactly as every other pack does.
|
|
5
|
+
//
|
|
6
|
+
// THE GROUNDING (platform.kimi.ai/docs/pricing/limits, read 2026-09-16): Moonshot publishes a
|
|
7
|
+
// per-tier table whose LOWEST published row (Tier 0, deposit ≥ $1) is concurrency 1, RPM 3,
|
|
8
|
+
// TPM 500,000, TPD 1,500,000. RPM 3 is an order of magnitude UNDER the kernel's undeclared
|
|
9
|
+
// fallback (30 calls/min), so the declaration buys resolution DOWNWARD from the fallback, never
|
|
10
|
+
// headroom: the ceiling is pinned at the kernel fallback in units, and the INFERENCE rules price
|
|
11
|
+
// the model-facing endpoints at a weight that keeps the real RPM-3 worst case inside the window.
|
|
12
|
+
// The tier table scales up to Tier 5 (RPM 300), but no caller's tier is knowable here — the
|
|
13
|
+
// budget protects the LOWEST tier by construction, and a higher tier is only ever under-spent.
|
|
14
|
+
// Web-search endpoints (POST /v1/tools/search, /v1/tools/search_pro, /v1/tools/fetch) are
|
|
15
|
+
// metered on their OWN QPS (QPS 1 at Tier 0), independent of the inference RPM — they get their
|
|
16
|
+
// own rule so a search sweep cannot be priced as a cheap read.
|
|
17
|
+
//
|
|
18
|
+
// THE 429 SHAPE (same limits page): a real 429 carries `X-RateLimit-Limit` /
|
|
19
|
+
// `X-RateLimit-Remaining` / `X-RateLimit-Reset` headers. `recordCall` reads the standard
|
|
20
|
+
// `retry-after` backstop plus these.
|
|
21
|
+
import {
|
|
22
|
+
declareRateBudget, rateBudgetPath, rateBudgetWeight,
|
|
23
|
+
RateBudget, RateBudgetError, type RateBudgetDeclaration, type RateBudgetOptions,
|
|
24
|
+
type RateBudgetReservation, type RateBudgetSnapshot,
|
|
25
|
+
} from '@volter/world-core';
|
|
26
|
+
|
|
27
|
+
const VENDOR = 'moonshot';
|
|
28
|
+
|
|
29
|
+
/** Rolling window, in ms. Spend older than this is pruned. */
|
|
30
|
+
export const MOONSHOT_BUDGET_WINDOW_MS = 60_000;
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Weighted units allowed inside one window. 60/60s at `defaultWeight` 2 = 30 calls a minute —
|
|
34
|
+
* EXACTLY the kernel's undeclared fallback. Moonshot DOES publish an RPM scalar (3 at Tier 0),
|
|
35
|
+
* but it is a CALL ceiling, not a weight-unit ceiling; pricing every call 2 keeps the fallback
|
|
36
|
+
* shape and lets the inference rules spend the allowance where the vendor actually meters it.
|
|
37
|
+
*/
|
|
38
|
+
export const MOONSHOT_BUDGET_CEILING = 60;
|
|
39
|
+
|
|
40
|
+
/** Seconds. A `retry-after` above this means the key is throttled hard — fail loudly, don't sleep. */
|
|
41
|
+
export const MOONSHOT_BUDGET_MAX_RETRY_AFTER_S = 300;
|
|
42
|
+
|
|
43
|
+
/** Per-call cost, keyed by `"<METHOD> <path>"`. See the header for what is documented vs. judged. */
|
|
44
|
+
export const MOONSHOT_CALL_WEIGHTS = {
|
|
45
|
+
/** Model-facing inference: /v1/chat/completions, /v1/responses, /anthropic/v1/messages.
|
|
46
|
+
* At 60/6 that is at most 10 inference calls a window — inside Tier 0's RPM 3×window shape
|
|
47
|
+
* for a single burst while leaving room for the interleaved reads a real client issues. */
|
|
48
|
+
inference: 6,
|
|
49
|
+
/** The web-search tool endpoints — Moonshot meters them on their OWN QPS (1 at Tier 0),
|
|
50
|
+
* independent of inference RPM. Priced like inference so a sweep cannot ride the read weight. */
|
|
51
|
+
webSearch: 6,
|
|
52
|
+
/** Everything else: models, files, batches, balance, token counting, signature verify. */
|
|
53
|
+
other: 2,
|
|
54
|
+
} as const;
|
|
55
|
+
|
|
56
|
+
/** THE PACK'S DECLARATION — pure data, the only Moonshot-specific thing in the whole budget. */
|
|
57
|
+
export const MOONSHOT_RATE_BUDGET: RateBudgetDeclaration = {
|
|
58
|
+
windowMs: MOONSHOT_BUDGET_WINDOW_MS,
|
|
59
|
+
ceiling: MOONSHOT_BUDGET_CEILING,
|
|
60
|
+
defaultWeight: MOONSHOT_CALL_WEIGHTS.other,
|
|
61
|
+
maxRetryAfterSeconds: MOONSHOT_BUDGET_MAX_RETRY_AFTER_S,
|
|
62
|
+
rules: [
|
|
63
|
+
// Anchored on Moonshot's REAL paths. Inference is token-metered (TPM 500,000 at Tier 0
|
|
64
|
+
// dwarfs RPM 3 as the binding constraint on real traffic), so model-facing POSTs cost 6.
|
|
65
|
+
{ match: '^POST /v1/chat/completions$', weight: MOONSHOT_CALL_WEIGHTS.inference },
|
|
66
|
+
{ match: '^POST /v1/responses$', weight: MOONSHOT_CALL_WEIGHTS.inference },
|
|
67
|
+
{ match: '^POST /anthropic/v1/messages$', weight: MOONSHOT_CALL_WEIGHTS.inference },
|
|
68
|
+
// Web-search tools: own QPS pool at the vendor, priced at inference weight here.
|
|
69
|
+
{ match: '^POST /v1/tools/search$', weight: MOONSHOT_CALL_WEIGHTS.webSearch },
|
|
70
|
+
{ match: '^POST /v1/tools/search_pro$', weight: MOONSHOT_CALL_WEIGHTS.webSearch },
|
|
71
|
+
{ match: '^POST /v1/tools/fetch$', weight: MOONSHOT_CALL_WEIGHTS.webSearch },
|
|
72
|
+
],
|
|
73
|
+
reason:
|
|
74
|
+
'Moonshot publishes a per-tier limit table (platform.kimi.ai/docs/pricing/limits, read ' +
|
|
75
|
+
'2026-09-16): Tier 0 (deposit ≥ $1) is concurrency 1, RPM 3, TPM 500,000, TPD 1,500,000, ' +
|
|
76
|
+
'Web Search QPS 1; Tier 5 (≥ $3000) is 100/300/5M/Unlimited/50. A 429 carries ' +
|
|
77
|
+
'X-RateLimit-Limit / X-RateLimit-Remaining / X-RateLimit-Reset. Because the LOWEST published ' +
|
|
78
|
+
'RPM (3) is far under the kernel fallback of 30 calls/min, the ceiling is pinned AT the ' +
|
|
79
|
+
'fallback (60 units / 60s, defaultWeight 2) and the declaration spends it downward: the three ' +
|
|
80
|
+
'model-facing inference endpoints (POST /v1/chat/completions, POST /v1/responses, ' +
|
|
81
|
+
'POST /anthropic/v1/messages) cost 6 each — at most 10 land in a window — because those calls ' +
|
|
82
|
+
'are token-metered (TPM) at the vendor and one call spends far more of a real account\'s ' +
|
|
83
|
+
'allowance than a list poll. The web-search tool endpoints get their own 6-weight rule ' +
|
|
84
|
+
'because Moonshot meters them on a SEPARATE QPS pool (1 at Tier 0), so a search sweep must ' +
|
|
85
|
+
'not be priceable as a cheap read. The per-tier judgment call is the strict direction: the ' +
|
|
86
|
+
'budget protects Tier 0 by construction and only ever under-spends a higher tier. The window ' +
|
|
87
|
+
'bounds the 60s AVERAGE and does not pace; a `retry-after` read off a real 429 is the ' +
|
|
88
|
+
'persisted cooldown backstop.',
|
|
89
|
+
};
|
|
90
|
+
|
|
91
|
+
// Declared at module load, so merely importing this module (which `moonshot-connector.ts` does)
|
|
92
|
+
// is enough to arm the real ceiling.
|
|
93
|
+
declareRateBudget(VENDOR, MOONSHOT_RATE_BUDGET);
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Price one call. The key is `"<METHOD> <path>"` with the query string split off, so a rule can
|
|
97
|
+
* price by method (a write is not a read) without the kernel knowing anything about Moonshot. An
|
|
98
|
+
* unclassified endpoint still costs `defaultWeight` — nothing is ever free.
|
|
99
|
+
*/
|
|
100
|
+
export function moonshotCallWeight(method: string, path: string): number {
|
|
101
|
+
const { bare, query } = moonshotSplitQuery(path);
|
|
102
|
+
// UPPER-CASE the method: `fetch` normalizes a known lowercase method before sending, so
|
|
103
|
+
// `execute('post', …)` really does issue a POST and must be priced as one.
|
|
104
|
+
return rateBudgetWeight(VENDOR, `${String(method).toUpperCase()} ${bare}`, query);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* `/v1/chat/completions?a=1` -> `{ bare: '/v1/chat/completions', query: { a: '1' } }`. Rules
|
|
109
|
+
* match the path; NORMALIZED, because the anchored rules are otherwise trivially evaded: `fetch`
|
|
110
|
+
* upper-cases a known method before sending, so `execute('post', …)` issues a real WRITE that a
|
|
111
|
+
* `^POST ` rule would price as a read; and a trailing slash makes a path miss a `$` anchor while
|
|
112
|
+
* most routers treat it as the same endpoint.
|
|
113
|
+
*/
|
|
114
|
+
function moonshotSplitQuery(path: string): { bare: string; query: Record<string, string> } {
|
|
115
|
+
const at = path.indexOf('?');
|
|
116
|
+
const query: Record<string, string> = {};
|
|
117
|
+
if (at !== -1) for (const [k, v] of new URLSearchParams(path.slice(at + 1))) query[k] = v;
|
|
118
|
+
// Collapse REPEATED slashes as well as a trailing one: `//v1/chat/completions` reaches the
|
|
119
|
+
// same endpoint on most routers but misses a `^POST /v1/...$` rule, which would price an
|
|
120
|
+
// inference call as a 2-unit read.
|
|
121
|
+
const raw = (at === -1 ? path : path.slice(0, at)).replace(/\/{2,}/g, '/');
|
|
122
|
+
const bare = raw.length > 1 && raw.endsWith('/') ? raw.replace(/\/+$/, '') : raw;
|
|
123
|
+
return { bare, query };
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/** Where Moonshot's ledger lives. Token-keyed and cwd-independent by default (Moonshot's limits
|
|
127
|
+
* are per ACCOUNT, so a cwd-scoped ledger would hand the same key a fresh allowance in every
|
|
128
|
+
* checkout, worktree and CI matrix leg); pass `root` for world-scoped accounting. */
|
|
129
|
+
export function moonshotBudgetPath(opts: { root?: string; token?: string } | string = {}): string {
|
|
130
|
+
const o = typeof opts === 'string' ? { root: opts } : opts;
|
|
131
|
+
// VENDOR spread LAST: a loosely-typed `{ vendor: 'other', … }` slipping through (TypeScript's
|
|
132
|
+
// excess-property check only catches object literals) must not redirect this pack's ledger.
|
|
133
|
+
return rateBudgetPath({ ...o, vendor: VENDOR });
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/** Construction options for Moonshot's budget. The vendor is fixed; everything else may only TIGHTEN. */
|
|
137
|
+
export type MoonshotBudgetOptions = Omit<RateBudgetOptions, 'vendor'>;
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* Moonshot's budget — the shared kernel guard bound to this vendor's declaration. A real
|
|
141
|
+
* subclass, not an alias, so `budget instanceof MoonshotBudget` in `liveMoonshotExecute` means
|
|
142
|
+
* "a budget that accounts against MOONSHOT's ledger under MOONSHOT's ceiling".
|
|
143
|
+
*/
|
|
144
|
+
export class MoonshotBudget extends RateBudget {
|
|
145
|
+
constructor(opts: MoonshotBudgetOptions = {}) {
|
|
146
|
+
super({
|
|
147
|
+
...opts,
|
|
148
|
+
// Field-per-line so the mutation gate can neuter the vendor binding: a budget whose
|
|
149
|
+
// `vendor` is not 'moonshot' accounts against another vendor's ledger, and the ceiling
|
|
150
|
+
// verify reads `err.vendor` off the refusal — the neuter must redden THAT assertion.
|
|
151
|
+
vendor: VENDOR,
|
|
152
|
+
});
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
/** The typed refusal. One error class shared with every other vendor's budget; `err.vendor` says
|
|
157
|
+
* which one refused, and `err.kind` says why. */
|
|
158
|
+
export { RateBudgetError as MoonshotBudgetError } from '@volter/world-core';
|
|
159
|
+
export type { RateBudgetErrorKind as MoonshotBudgetErrorKind } from '@volter/world-core';
|
|
160
|
+
/** Re-exported so a caller can age a window out against the DECLARED window, not a hard-coded 60_000. */
|
|
161
|
+
export { MOONSHOT_BUDGET_WINDOW_MS as MOONSHOT_RATE_BUDGET_WINDOW_MS };
|
|
162
|
+
export type MoonshotBudgetReservation = RateBudgetReservation;
|
|
163
|
+
export type MoonshotBudgetSnapshot = RateBudgetSnapshot;
|