@qoder-ai/qmind-cli 2.0.0 → 3.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.
@@ -0,0 +1,17 @@
1
+ import { H as sourceUploadCapabilitiesSchema, m as requiredString, n as call } from "./shared-BkQJzIZF.js";
2
+ //#region ../sdk/src/operations/source-upload.ts
3
+ /** Notebook-scoped edit preflight, not the deployment-level capabilities lookup. */
4
+ async function preflightSourceUpload(context, notebookId, options) {
5
+ const notebook = requiredString(notebookId, "notebookId");
6
+ return call(context, {
7
+ callOptions: options,
8
+ capability: "transfers",
9
+ idempotent: true,
10
+ method: "GET",
11
+ operation: "sources.uploadPreflight",
12
+ path: context.profile.apiPath("notebooks", notebook, "sources", "upload", "capabilities"),
13
+ schema: sourceUploadCapabilitiesSchema
14
+ });
15
+ }
16
+ //#endregion
17
+ export { preflightSourceUpload as t };
@@ -0,0 +1,237 @@
1
+ import { $ as QMIND_SOURCE_UPLOAD_MAX_BYTES, B as sourceSchema, E as imageUploadTicketSchema, Q as QMIND_MULTIPART_UPLOAD_MAX_BYTES, U as sourceUploadTicketSchema, X as voidResponseSchema, a as invalidArgument, l as optionalString, m as requiredString, nt as executeUnary, p as queryPath, r as compactUndefined, rt as QMindError, u as optionalTrimmedString } from "./shared-BkQJzIZF.js";
2
+ import { t as preflightSourceUpload } from "./source-upload-DBm1Hyki.js";
3
+ //#region ../sdk/src/operations/transfers.ts
4
+ function isRecord(value) {
5
+ return typeof value === "object" && value !== null && !Array.isArray(value);
6
+ }
7
+ function binarySource(value) {
8
+ if (!isRecord(value)) throw invalidArgument("source must be a QMindBinarySource", "source");
9
+ const filename = requiredString(value.filename, "source.filename");
10
+ const mimeType = requiredString(value.mimeType, "source.mimeType");
11
+ const size = value.size;
12
+ if (typeof size !== "number" || !Number.isSafeInteger(size) || size <= 0) throw invalidArgument("source.size must be a positive safe integer", "source.size");
13
+ if (typeof value.open !== "function") throw invalidArgument("source.open must be a function", "source.open");
14
+ const source = value;
15
+ return {
16
+ filename,
17
+ mimeType,
18
+ open: () => source.open(),
19
+ size
20
+ };
21
+ }
22
+ function transferOptions(options) {
23
+ if (options === void 0) return {};
24
+ if (!isRecord(options)) throw invalidArgument("options must be an object", "options");
25
+ if (options.onProgress !== void 0 && typeof options.onProgress !== "function") throw invalidArgument("onProgress must be a function", "onProgress");
26
+ return options;
27
+ }
28
+ function uploadMetadata(value) {
29
+ if (value === void 0) return {};
30
+ if (!isRecord(value)) throw invalidArgument("metadata must be an object", "metadata");
31
+ return {
32
+ parentSourceId: optionalString(value.parentSourceId, "parentSourceId"),
33
+ path: optionalString(value.path, "path"),
34
+ sha256: optionalTrimmedString(value.sha256, "sha256"),
35
+ sourceType: optionalTrimmedString(value.sourceType, "sourceType"),
36
+ title: optionalTrimmedString(value.title, "title")
37
+ };
38
+ }
39
+ function progressReporter(context, operation, strategy, totalBytes, options) {
40
+ let lastUploadedBytes = 0;
41
+ const notify = (phase, uploadedBytes) => {
42
+ if (!Number.isSafeInteger(uploadedBytes) || uploadedBytes < lastUploadedBytes) throw new QMindError("HOST_POLICY_ERROR", "upload progress must be a monotonic integer", {
43
+ access: context.profile.access,
44
+ details: {
45
+ lastUploadedBytes,
46
+ totalBytes,
47
+ uploadedBytes
48
+ },
49
+ operation
50
+ });
51
+ if (uploadedBytes > totalBytes) throw new QMindError("HOST_POLICY_ERROR", "upload progress exceeds source size", {
52
+ access: context.profile.access,
53
+ details: {
54
+ totalBytes,
55
+ uploadedBytes
56
+ },
57
+ operation
58
+ });
59
+ lastUploadedBytes = uploadedBytes;
60
+ const progress = {
61
+ phase,
62
+ strategy,
63
+ totalBytes,
64
+ uploadedBytes
65
+ };
66
+ options.onProgress?.(progress);
67
+ };
68
+ return {
69
+ complete: () => notify("uploading", totalBytes),
70
+ registering: () => notify("registering", totalBytes),
71
+ start: () => notify("uploading", 0),
72
+ update: (uploadedBytes) => notify("uploading", uploadedBytes)
73
+ };
74
+ }
75
+ function requestOptions(options) {
76
+ return {
77
+ ...options.signal === void 0 ? {} : { signal: options.signal },
78
+ ...options.timeoutMs === void 0 ? {} : { timeoutMs: options.timeoutMs }
79
+ };
80
+ }
81
+ function transferCall(context, operation, request, schema) {
82
+ return executeUnary({
83
+ capability: operation.startsWith("images.") ? "images.presignUpload" : "transfers",
84
+ operation,
85
+ profile: context.profile,
86
+ request,
87
+ schema,
88
+ transport: context.transport
89
+ });
90
+ }
91
+ function hostRequest(path, method, idempotent, options, body) {
92
+ const authorization = options.authorization?.trim();
93
+ return {
94
+ body,
95
+ destination: {
96
+ kind: "host",
97
+ path
98
+ },
99
+ ...authorization === void 0 || authorization.length === 0 ? {} : { headers: { authorization: `Bearer ${authorization}` } },
100
+ idempotent,
101
+ method,
102
+ ...requestOptions(options)
103
+ };
104
+ }
105
+ function signedUploadRequest(ticket, source, progress, options) {
106
+ return {
107
+ body: {
108
+ kind: "binary",
109
+ onProgress: progress.update,
110
+ source
111
+ },
112
+ destination: {
113
+ kind: "signed",
114
+ url: ticket.uploadUrl
115
+ },
116
+ headers: ticket.headers,
117
+ idempotent: true,
118
+ method: "PUT",
119
+ ...requestOptions(options)
120
+ };
121
+ }
122
+ function selectSourceUploadStrategy(source, capabilities) {
123
+ const methods = capabilities.uploadMethods.filter(({ available, method, maxSizeBytes }) => available && maxSizeBytes > 0 && (method === "multipart" || method === "presign"));
124
+ if (methods.length === 0) throw new QMindError("UNSUPPORTED_OPERATION", "source upload is unavailable", {
125
+ details: { reason: "upload-unavailable" },
126
+ operation: "sources.uploadPreflight"
127
+ });
128
+ const filename = source.filename.toLowerCase();
129
+ const matching = methods.filter(({ supportedExtensions }) => supportedExtensions.some((extension) => filename.endsWith(extension.toLowerCase())));
130
+ if (matching.length === 0) throw new QMindError("INVALID_ARGUMENT", "file extension is not supported for upload", {
131
+ details: { reason: "unsupported-extension" },
132
+ operation: "sources.uploadPreflight"
133
+ });
134
+ const limit = (method) => Math.min(method.maxSizeBytes, method.method === "multipart" ? QMIND_MULTIPART_UPLOAD_MAX_BYTES : QMIND_SOURCE_UPLOAD_MAX_BYTES);
135
+ if (matching.find((method) => method.method === "multipart" && source.size <= limit(method))) return "multipart";
136
+ if (matching.some((method) => method.method === "presign" && source.size <= limit(method))) return "presigned";
137
+ throw new QMindError("FILE_TOO_LARGE", "file exceeds the available upload size limit", {
138
+ details: {
139
+ limitBytes: Math.max(...matching.map(limit)),
140
+ size: source.size
141
+ },
142
+ operation: "sources.uploadPreflight"
143
+ });
144
+ }
145
+ function createTransferOperations(context) {
146
+ return {
147
+ async uploadImage(notebookId, sourceId, rawSource, rawOptions) {
148
+ const notebook = requiredString(notebookId, "notebookId");
149
+ const sourceIdValue = requiredString(sourceId, "sourceId");
150
+ const source = binarySource(rawSource);
151
+ const options = transferOptions(rawOptions);
152
+ if (source.size > 52428800) throw new QMindError("FILE_TOO_LARGE", "image exceeds the 50 MiB upload limit", {
153
+ access: context.profile.access,
154
+ details: {
155
+ limitBytes: QMIND_MULTIPART_UPLOAD_MAX_BYTES,
156
+ size: source.size
157
+ },
158
+ operation: "images.upload"
159
+ });
160
+ const ticket = await transferCall(context, "images.presignUpload", hostRequest(context.profile.apiPath("notebooks", notebook, "sources", sourceIdValue, "images", "presign"), "POST", false, options, {
161
+ kind: "json",
162
+ value: {
163
+ filename: source.filename,
164
+ mimeType: source.mimeType,
165
+ size: source.size
166
+ }
167
+ }), imageUploadTicketSchema);
168
+ const progress = progressReporter(context, "images.upload", "presigned", source.size, options);
169
+ progress.start();
170
+ await transferCall(context, "images.upload", signedUploadRequest(ticket, source, progress, options), voidResponseSchema);
171
+ progress.complete();
172
+ return ticket;
173
+ },
174
+ async uploadSource(notebookId, rawSource, rawMetadata, rawOptions) {
175
+ const notebook = requiredString(notebookId, "notebookId");
176
+ const source = binarySource(rawSource);
177
+ const metadata = uploadMetadata(rawMetadata);
178
+ const options = transferOptions(rawOptions);
179
+ if (source.size > 524288e3) throw new QMindError("FILE_TOO_LARGE", "file exceeds the 500 MiB upload limit", {
180
+ access: context.profile.access,
181
+ details: {
182
+ limitBytes: QMIND_SOURCE_UPLOAD_MAX_BYTES,
183
+ size: source.size
184
+ },
185
+ operation: "transfers.source.multipart"
186
+ });
187
+ if (selectSourceUploadStrategy(source, await preflightSourceUpload(context, notebook, options)) === "multipart") {
188
+ const progress = progressReporter(context, "transfers.source.multipart", "multipart", source.size, options);
189
+ progress.start();
190
+ const fields = compactUndefined({
191
+ parent_source_id: metadata.parentSourceId,
192
+ path: metadata.path,
193
+ retain_original_file: "true",
194
+ source_type: metadata.sourceType,
195
+ title: metadata.title
196
+ });
197
+ const result = await transferCall(context, "transfers.source.multipart", hostRequest(queryPath(context.profile.apiPath("notebooks", notebook, "sources", "upload"), [["client", context.client]]), "POST", false, options, {
198
+ file: source,
199
+ fields,
200
+ kind: "multipart",
201
+ onProgress: progress.update
202
+ }), sourceSchema);
203
+ progress.complete();
204
+ return result;
205
+ }
206
+ const ticket = await transferCall(context, "transfers.source.presign", hostRequest(context.profile.apiPath("notebooks", notebook, "sources", "presign"), "POST", false, options, {
207
+ kind: "json",
208
+ value: {
209
+ filename: source.filename,
210
+ mimeType: source.mimeType,
211
+ size: source.size
212
+ }
213
+ }), sourceUploadTicketSchema);
214
+ const progress = progressReporter(context, "transfers.source.upload", "presigned", source.size, options);
215
+ progress.start();
216
+ await transferCall(context, "transfers.source.upload", signedUploadRequest(ticket, source, progress, options), voidResponseSchema);
217
+ progress.complete();
218
+ progress.registering();
219
+ return transferCall(context, "transfers.source.register", hostRequest(context.profile.apiPath("notebooks", notebook, "sources", "register"), "POST", false, options, {
220
+ kind: "json",
221
+ value: compactUndefined({
222
+ filename: source.filename,
223
+ mimeType: source.mimeType,
224
+ parentSourceId: metadata.parentSourceId,
225
+ path: metadata.path,
226
+ sha256: metadata.sha256,
227
+ size: source.size,
228
+ sourceType: metadata.sourceType,
229
+ title: metadata.title,
230
+ uri: ticket.uri
231
+ })
232
+ }), sourceSchema);
233
+ }
234
+ };
235
+ }
236
+ //#endregion
237
+ export { createTransferOperations };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@qoder-ai/qmind-cli",
3
- "version": "2.0.0",
3
+ "version": "3.1.0",
4
4
  "description": "QMind command line interface",
5
5
  "bin": {
6
6
  "qmind": "bin/cli.js"
@@ -11,7 +11,9 @@
11
11
  "bin",
12
12
  "dist",
13
13
  "CHANGELOG.md",
14
- "README.md"
14
+ "CHANGELOG.zh-CN.md",
15
+ "README.md",
16
+ "README.zh-CN.md"
15
17
  ],
16
18
  "engines": {
17
19
  "node": ">=20.18.1"
@@ -31,20 +33,12 @@
31
33
  "undici": "7.29.0",
32
34
  "zod": "4.3.6"
33
35
  },
34
- "optionalDependencies": {
35
- "@napi-rs/keyring": "1.3.0"
36
- },
37
36
  "devDependencies": {
38
37
  "@types/node": "24.13.3",
39
38
  "@types/picomatch": "4.0.3",
40
39
  "@types/proper-lockfile": "4.1.4",
41
- "@ali/qmind-node-host": "^2.0.0",
42
- "@ali/qmind-sdk": "^0.2.0"
40
+ "@ali/qmind-node-host": "^2.2.8",
41
+ "@ali/qmind-sdk": "^0.14.0"
43
42
  },
44
- "license": "UNLICENSED",
45
- "scripts": {
46
- "build": "tsdown",
47
- "check:package": "publint",
48
- "typecheck": "tsc --noEmit -p tsconfig.json"
49
- }
43
+ "license": "UNLICENSED"
50
44
  }