@eddyskywalker/dsh-chatgpt-subscription 0.40.1 → 0.40.2

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.
@@ -0,0 +1,232 @@
1
+ /**
2
+ * Kimi Code hosted-file (media upload) client.
3
+ *
4
+ * WHY THIS FILE EXISTS. Everything this plugin has ever sent to the Kimi Code
5
+ * gateway carries media INLINE, as a `data:<type>;base64,...` URL
6
+ * (mapper.ts builds both the image and the video block that way). The gateway
7
+ * also accepts a hosted reference instead: upload the bytes once, cite
8
+ * `ms://<id>` afterwards. That is the only way to send a clip whose base64 would
9
+ * overrun the request body ceiling, and it is what this module implements.
10
+ *
11
+ * EVERY WIRE FACT BELOW WAS MEASURED live against the subscription gateway by
12
+ * `scripts/probe-kimi-files.probe.ts` on this line's own account and region,
13
+ * not copied from the official client:
14
+ *
15
+ * - the upload is `POST {coding base}/v1/files`, `multipart/form-data` with a
16
+ * `file` part and a `purpose` part whose value is `image` or `video`
17
+ * (note: NOT the `*_understanding` vocabulary the MiniMax line uses);
18
+ * - it authenticates with the bearer token plus the same `X-Msh-*` identity
19
+ * headers every other request on this line sends;
20
+ * - the body answers with the id at the top level or nested under
21
+ * `data`/`file`/`result`, and the reference a later chat body cites is
22
+ * `ms://<id>`;
23
+ * - `content-type` is never set by hand: the runtime writes
24
+ * `multipart/form-data; boundary=...` itself, and a hand-written JSON header
25
+ * on a form body is a malformed request.
26
+ *
27
+ * DELIBERATELY NARROW. No adapter, mapper or route is touched here: this module
28
+ * only uploads, caches and returns a reference. Integration is a later change.
29
+ *
30
+ * @module dsh-chatgpt-subscription/kimi-media-files
31
+ */
32
+ /** Upload route, relative to a base URL that may already carry `/v1`. */
33
+ export declare const FILES_UPLOAD_PATH = "/v1/files";
34
+ /** Scheme a stored id is cited by on the wire. */
35
+ export declare const FILE_REF_SCHEME = "ms://";
36
+ /**
37
+ * Ceiling on cached references.
38
+ *
39
+ * Bounded because a long conversation can attach many distinct clips and a
40
+ * process that lives for days must not grow one map per upload forever. 256 is
41
+ * far more than one conversation cites and small enough to be invisible.
42
+ */
43
+ export declare const DEFAULT_MEDIA_FILE_CACHE_ENTRIES = 256;
44
+ /** Time budget for one upload request. */
45
+ export declare const MEDIA_UPLOAD_TIMEOUT_MS = 120000;
46
+ /** The two purposes this surface measuredly accepts. */
47
+ export type KimiMediaKind = 'image' | 'video';
48
+ /** Why one upload could not produce a usable reference. */
49
+ export type KimiMediaUploadFailure =
50
+ /** The request was unusable before anything was sent (media type, empty body, ...). */
51
+ 'invalid-request'
52
+ /** The media type is not image/* or video/*, so there is no purpose to send. */
53
+ | 'unsupported-media'
54
+ /** HTTP 401/403: the credential was not accepted. */
55
+ | 'unauthorized'
56
+ /** HTTP 404/405: this base URL does not expose the upload route. */
57
+ | 'not-found'
58
+ /** HTTP 413: the service refused the body as too large. */
59
+ | 'too-large'
60
+ /** HTTP 5xx: the service failed; the same request may succeed later. */
61
+ | 'server'
62
+ /** Any other non-2xx answer. */
63
+ | 'rejected'
64
+ /** 2xx whose body was not JSON. */
65
+ | 'not-json'
66
+ /** 2xx whose body carried no usable id. */
67
+ | 'no-id'
68
+ /** The local time budget elapsed before the response arrived. */
69
+ | 'timeout'
70
+ /** The request never completed (socket, DNS, TLS, ...). */
71
+ | 'network';
72
+ /**
73
+ * One failed upload.
74
+ *
75
+ * Carries the HTTP status and a REDACTED digest of the response body so a
76
+ * caller can decide (401 -> sign in again, 5xx -> retry) without the raw body
77
+ * reaching a log. The access token is never placed in `message` or `detail`;
78
+ * see {@link redact}.
79
+ */
80
+ export declare class KimiMediaUploadError extends Error {
81
+ readonly failure: KimiMediaUploadFailure;
82
+ readonly status: number | null;
83
+ /** Redacted one-line digest of the response body, or '' when there was none. */
84
+ readonly detail: string;
85
+ constructor(failure: KimiMediaUploadFailure, message: string, options?: {
86
+ status?: number | null;
87
+ detail?: string;
88
+ });
89
+ }
90
+ /** What one upload needs. */
91
+ export interface KimiMediaUploadRequest {
92
+ /** Bearer token the caller obtained; never logged and never part of a key. */
93
+ readonly accessToken: string;
94
+ /** Coding API base, with or without a trailing slash and with or without `/v1`. */
95
+ readonly baseUrl: string;
96
+ /**
97
+ * Opaque, stable identifier of the ACCOUNT the bytes are uploaded under.
98
+ *
99
+ * It scopes the cache: an id minted for one account is never handed to
100
+ * another, because nothing here proves the gateway shares ids across accounts.
101
+ * It must identify the account (a user id, an account slot index) and must
102
+ * NEVER be the access token — {@link KimiMediaFilesUploader.upload} refuses
103
+ * that, so a token cannot end up inside a cache key by accident.
104
+ */
105
+ readonly accountScope: string;
106
+ /** Complete file bytes. */
107
+ readonly data: Uint8Array;
108
+ /** Media type, such as `image/png` or `video/mp4`. */
109
+ readonly mediaType: string;
110
+ /** Optional display name; only its leaf is used, and never as a path. */
111
+ readonly filename?: string;
112
+ /** Cancels this caller's wait, and the upload once nobody is waiting. */
113
+ readonly signal?: AbortSignal;
114
+ }
115
+ /** Everything a caller of this module may swap out, plus test seams. */
116
+ export interface KimiMediaUploadOptions {
117
+ /** Transport; defaults to the global `fetch`. */
118
+ readonly fetchFn?: typeof fetch;
119
+ /** Cache ceiling; defaults to {@link DEFAULT_MEDIA_FILE_CACHE_ENTRIES}. */
120
+ readonly maxCacheEntries?: number;
121
+ /** Per-request time budget; 0 (or a negative value) disables it. */
122
+ readonly timeoutMs?: number;
123
+ }
124
+ /**
125
+ * The upload URL for one coding base.
126
+ *
127
+ * The base this line resolves normally carries neither a version nor a trailing
128
+ * slash (`https://api.kimi.com/coding`), so the measured route is built by
129
+ * appending `/v1/files` and the answer is `.../coding/v1/files`. A caller that
130
+ * passes a base which ALREADY ends in `/v1` — the official client's own
131
+ * KIMI_CODE_BASE_URL shape — gets the prefix removed once rather than doubled:
132
+ * `.../coding/v1/v1/files` is a 404 that reads like a missing route.
133
+ */
134
+ export declare function filesUploadUrl(baseUrl: string): string;
135
+ /** The upload purpose one media type maps to, or undefined when unsupported. */
136
+ export declare function mediaKindFor(mediaType: string): KimiMediaKind | undefined;
137
+ /** The wire reference for a stored id. */
138
+ export declare function fileReference(id: string): string;
139
+ /**
140
+ * The cache key for one upload.
141
+ *
142
+ * Five things, and each of them is load-bearing:
143
+ *
144
+ * - ACCOUNT SCOPE, because an id minted for one account is never assumed to be
145
+ * readable by another (see {@link KimiMediaUploadRequest.accountScope});
146
+ * - BASE URL (normalized through {@link filesUploadUrl}, so a trailing slash
147
+ * cannot split one endpoint into two slots), because two regions or two
148
+ * deployments are two distinct file stores;
149
+ * - MEDIA KIND and MEDIA TYPE, because the same bytes uploaded as an image and
150
+ * as a video are two different resources as far as the purpose is concerned;
151
+ * - CONTENT SHA-256, so identical bytes reuse one upload and any change at all
152
+ * misses the cache.
153
+ *
154
+ * The access token is NOT part of it: a key is written to memory only, but a
155
+ * token in a key is a token in the next dump, and the account is already
156
+ * identified by its scope. `|` cannot appear in a scope because the scope is
157
+ * sanitized here.
158
+ */
159
+ export declare function mediaFileCacheKey(input: {
160
+ accountScope: string;
161
+ baseUrl: string;
162
+ mediaType: string;
163
+ data: Uint8Array;
164
+ }): string;
165
+ /**
166
+ * Uploads one media file per distinct key, caching and single-flighting them.
167
+ *
168
+ * Two behaviours are deliberate and easy to get wrong:
169
+ *
170
+ * 1. SINGLE FLIGHT. Concurrent callers with the same key share one request.
171
+ * A caller that aborts stops waiting immediately and gets an AbortError, but
172
+ * the shared request is only cancelled once NO caller is still waiting — so
173
+ * one cancelled turn does not fail another turn that wants the same clip.
174
+ * 2. NO TTL. A successful reference is cached until it is evicted by the LRU
175
+ * ceiling. Inventing an expiry would either expire a live id (a failed turn)
176
+ * or outlive it silently; nothing here measured a lifetime, so nothing here
177
+ * pretends to know one. A reference the service later rejects is dropped with
178
+ * {@link forget} by whoever sees that rejection.
179
+ */
180
+ export declare class KimiMediaFilesUploader {
181
+ private readonly fetchFn;
182
+ private readonly maxCacheEntries;
183
+ private readonly timeoutMs;
184
+ /** key -> `ms://<id>`, in least-recently-used order. */
185
+ private readonly cache;
186
+ private readonly flights;
187
+ constructor(options?: KimiMediaUploadOptions);
188
+ /** Cached references currently held, for a settings surface or a test. */
189
+ get size(): number;
190
+ /** Uploads one file and returns the `ms://<id>` reference a request cites. */
191
+ upload(request: KimiMediaUploadRequest): Promise<string>;
192
+ /**
193
+ * Drop the reference one upload would use.
194
+ *
195
+ * The caller that sees a stored id REJECTED on the wire calls this so the next
196
+ * attempt uploads again instead of replaying the same dead reference.
197
+ */
198
+ forget(request: Omit<KimiMediaUploadRequest, 'accessToken'>): boolean;
199
+ /** Forget everything; a sign-out or a test starting over. */
200
+ clear(): void;
201
+ /** Validate and normalize one request, or throw before anything is sent. */
202
+ private resolve;
203
+ private readCache;
204
+ private writeCache;
205
+ /** Open one flight and run it; the map entry is dropped as it settles. */
206
+ private startFlight;
207
+ /**
208
+ * Wait for one flight, respecting THIS caller's abort.
209
+ *
210
+ * The flight is not bound to the caller's signal: a caller that aborts
211
+ * detaches, and the transport is cancelled only when the last waiter has gone.
212
+ */
213
+ private join;
214
+ /** The one request a flight performs. */
215
+ private performUpload;
216
+ /** Turn a transport rejection into the error the caller must see. */
217
+ private transportFailure;
218
+ /** Read one response into a reference, or into a typed failure. */
219
+ private settle;
220
+ }
221
+ /** One uploader with the default transport and cache size. */
222
+ export declare function createMediaFilesUploader(options?: KimiMediaUploadOptions): KimiMediaFilesUploader;
223
+ /**
224
+ * The process-wide uploader the adapter will use.
225
+ *
226
+ * Shared on purpose: the cache is the whole point, and a per-call instance would
227
+ * upload the same clip once per turn.
228
+ */
229
+ export declare const kimiMediaFiles: KimiMediaFilesUploader;
230
+ /** Upload one file through the shared uploader. */
231
+ export declare function uploadMediaFile(request: KimiMediaUploadRequest): Promise<string>;
232
+ //# sourceMappingURL=media-files.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"media-files.d.ts","sourceRoot":"","sources":["../../../../src/host/kimi-code/media-files.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG;AAKH,yEAAyE;AACzE,eAAO,MAAM,iBAAiB,cAAc,CAAA;AAE5C,kDAAkD;AAClD,eAAO,MAAM,eAAe,UAAU,CAAA;AAEtC;;;;;;GAMG;AACH,eAAO,MAAM,gCAAgC,MAAM,CAAA;AAEnD,0CAA0C;AAC1C,eAAO,MAAM,uBAAuB,SAAU,CAAA;AAE9C,wDAAwD;AACxD,MAAM,MAAM,aAAa,GAAG,OAAO,GAAG,OAAO,CAAA;AAE7C,2DAA2D;AAC3D,MAAM,MAAM,sBAAsB;AAChC,uFAAuF;AACrF,iBAAiB;AACnB,gFAAgF;GAC9E,mBAAmB;AACrB,qDAAqD;GACnD,cAAc;AAChB,oEAAoE;GAClE,WAAW;AACb,2DAA2D;GACzD,WAAW;AACb,wEAAwE;GACtE,QAAQ;AACV,gCAAgC;GAC9B,UAAU;AACZ,mCAAmC;GACjC,UAAU;AACZ,2CAA2C;GACzC,OAAO;AACT,iEAAiE;GAC/D,SAAS;AACX,2DAA2D;GACzD,SAAS,CAAA;AAEb;;;;;;;GAOG;AACH,qBAAa,oBAAqB,SAAQ,KAAK;IAC7C,QAAQ,CAAC,OAAO,EAAE,sBAAsB,CAAA;IACxC,QAAQ,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,CAAA;IAC9B,gFAAgF;IAChF,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;gBAGrB,OAAO,EAAE,sBAAsB,EAC/B,OAAO,EAAE,MAAM,EACf,OAAO,GAAE;QAAE,MAAM,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;QAAC,MAAM,CAAC,EAAE,MAAM,CAAA;KAAO;CAQ5D;AAED,6BAA6B;AAC7B,MAAM,WAAW,sBAAsB;IACrC,8EAA8E;IAC9E,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAA;IAC5B,mFAAmF;IACnF,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAA;IACxB;;;;;;;;OAQG;IACH,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAA;IAC7B,2BAA2B;IAC3B,QAAQ,CAAC,IAAI,EAAE,UAAU,CAAA;IACzB,sDAAsD;IACtD,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAA;IAC1B,yEAAyE;IACzE,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAA;IAC1B,yEAAyE;IACzE,QAAQ,CAAC,MAAM,CAAC,EAAE,WAAW,CAAA;CAC9B;AAED,wEAAwE;AACxE,MAAM,WAAW,sBAAsB;IACrC,iDAAiD;IACjD,QAAQ,CAAC,OAAO,CAAC,EAAE,OAAO,KAAK,CAAA;IAC/B,2EAA2E;IAC3E,QAAQ,CAAC,eAAe,CAAC,EAAE,MAAM,CAAA;IACjC,oEAAoE;IACpE,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAA;CAC5B;AA+BD;;;;;;;;;GASG;AACH,wBAAgB,cAAc,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAWtD;AAMD,gFAAgF;AAChF,wBAAgB,YAAY,CAAC,SAAS,EAAE,MAAM,GAAG,aAAa,GAAG,SAAS,CAKzE;AA+ID,0CAA0C;AAC1C,wBAAgB,aAAa,CAAC,EAAE,EAAE,MAAM,GAAG,MAAM,CAEhD;AAWD;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAgB,iBAAiB,CAAC,KAAK,EAAE;IACvC,YAAY,EAAE,MAAM,CAAA;IACpB,OAAO,EAAE,MAAM,CAAA;IACf,SAAS,EAAE,MAAM,CAAA;IACjB,IAAI,EAAE,UAAU,CAAA;CACjB,GAAG,MAAM,CAWT;AA6BD;;;;;;;;;;;;;;GAcG;AACH,qBAAa,sBAAsB;IACjC,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAc;IACtC,OAAO,CAAC,QAAQ,CAAC,eAAe,CAAQ;IACxC,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAQ;IAClC,wDAAwD;IACxD,OAAO,CAAC,QAAQ,CAAC,KAAK,CAA4B;IAClD,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAkC;gBAE9C,OAAO,GAAE,sBAA2B;IAUhD,0EAA0E;IAC1E,IAAI,IAAI,IAAI,MAAM,CAEjB;IAED,8EAA8E;IACxE,MAAM,CAAC,OAAO,EAAE,sBAAsB,GAAG,OAAO,CAAC,MAAM,CAAC;IAkB9D;;;;;OAKG;IACH,MAAM,CAAC,OAAO,EAAE,IAAI,CAAC,sBAAsB,EAAE,aAAa,CAAC,GAAG,OAAO;IAerE,6DAA6D;IAC7D,KAAK,IAAI,IAAI;IAMb,4EAA4E;IAC5E,OAAO,CAAC,OAAO;IA2Cf,OAAO,CAAC,SAAS;IASjB,OAAO,CAAC,UAAU;IAUlB,0EAA0E;IAC1E,OAAO,CAAC,WAAW;IAanB;;;;;OAKG;YACW,IAAI;IA6ClB,yCAAyC;YAC3B,aAAa;IA0D3B,qEAAqE;IACrE,OAAO,CAAC,gBAAgB;IA0BxB,mEAAmE;IACnE,OAAO,CAAC,MAAM;CA6Df;AAED,8DAA8D;AAC9D,wBAAgB,wBAAwB,CAAC,OAAO,GAAE,sBAA2B,GAAG,sBAAsB,CAErG;AAED;;;;;GAKG;AACH,eAAO,MAAM,cAAc,wBAA6B,CAAA;AAExD,mDAAmD;AACnD,wBAAgB,eAAe,CAAC,OAAO,EAAE,sBAAsB,GAAG,OAAO,CAAC,MAAM,CAAC,CAEhF"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@eddyskywalker/dsh-chatgpt-subscription",
3
- "version": "0.40.1",
3
+ "version": "0.40.2",
4
4
  "description": "DSH provider plugin for Codex access through a ChatGPT subscription.",
5
5
  "icon": "./icon.svg",
6
6
  "author": {