dsh-qoder-connect 0.1.2 → 0.1.5
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/README.en.md +8 -0
- package/README.md +156 -148
- package/assets/7.png +0 -0
- package/lib/bin.js +1 -1
- package/lib/client.js +1449 -1217
- package/lib/index.d.ts +40 -1
- package/lib/index.js +280 -7
- package/lib/{variants-C0iZD8AD.js → variants-BEiQve5M.js} +226 -6
- package/package.json +2 -2
|
@@ -876,6 +876,14 @@ function getQoderUserStatusUrl(region = "global") {
|
|
|
876
876
|
const { openApiUrl } = resolveQoderEndpoints(region);
|
|
877
877
|
return `${openApiUrl}/api/v3/user/status`;
|
|
878
878
|
}
|
|
879
|
+
function getQoderCampaignsUrl(region = "global") {
|
|
880
|
+
const { openApiUrl } = resolveQoderEndpoints(region);
|
|
881
|
+
return `${openApiUrl}/sash/api/v1/me/campaigns`;
|
|
882
|
+
}
|
|
883
|
+
function getQoderClaimCampaignUrl(region = "global", campaignId) {
|
|
884
|
+
const { openApiUrl } = resolveQoderEndpoints(region);
|
|
885
|
+
return `${openApiUrl}/sash/api/v1/me/campaigns/${encodeURIComponent(campaignId)}/claim`;
|
|
886
|
+
}
|
|
879
887
|
//#endregion
|
|
880
888
|
//#region src/qoder/errors.ts
|
|
881
889
|
/**
|
|
@@ -2408,6 +2416,131 @@ var QoderUsageReader = class {
|
|
|
2408
2416
|
}
|
|
2409
2417
|
};
|
|
2410
2418
|
//#endregion
|
|
2419
|
+
//#region src/qoder/transport/checkin.ts
|
|
2420
|
+
const defaultCheckInTimeoutMs = 15e3;
|
|
2421
|
+
var QoderCheckInService = class {
|
|
2422
|
+
authService;
|
|
2423
|
+
fetchImpl;
|
|
2424
|
+
region;
|
|
2425
|
+
variantId;
|
|
2426
|
+
logger;
|
|
2427
|
+
timeoutMs;
|
|
2428
|
+
constructor(options) {
|
|
2429
|
+
this.authService = options.authService;
|
|
2430
|
+
this.fetchImpl = options.fetch ?? globalThis.fetch;
|
|
2431
|
+
this.region = options.region ?? "china";
|
|
2432
|
+
this.variantId = options.variantId ?? "qoder";
|
|
2433
|
+
this.logger = options.logger;
|
|
2434
|
+
this.timeoutMs = options.timeoutMs ?? defaultCheckInTimeoutMs;
|
|
2435
|
+
}
|
|
2436
|
+
async fetchCampaigns(token, signal) {
|
|
2437
|
+
const url = getQoderCampaignsUrl(this.region);
|
|
2438
|
+
const data = await openApiJsonRequest(this.fetchImpl, {
|
|
2439
|
+
url,
|
|
2440
|
+
token,
|
|
2441
|
+
signal,
|
|
2442
|
+
timeoutMs: this.timeoutMs,
|
|
2443
|
+
logger: this.logger,
|
|
2444
|
+
operation: "Campaigns",
|
|
2445
|
+
logCategory: "campaigns.list"
|
|
2446
|
+
});
|
|
2447
|
+
return Array.isArray(data?.campaigns) ? data.campaigns : [];
|
|
2448
|
+
}
|
|
2449
|
+
async claimCampaign(token, campaignId, signal) {
|
|
2450
|
+
const url = getQoderClaimCampaignUrl(this.region, campaignId);
|
|
2451
|
+
const { openApiUrl } = resolveQoderEndpoints(this.region);
|
|
2452
|
+
return openApiJsonRequest(this.fetchImpl, {
|
|
2453
|
+
url,
|
|
2454
|
+
method: "POST",
|
|
2455
|
+
token,
|
|
2456
|
+
headers: { origin: openApiUrl },
|
|
2457
|
+
signal,
|
|
2458
|
+
timeoutMs: this.timeoutMs,
|
|
2459
|
+
logger: this.logger,
|
|
2460
|
+
operation: "ClaimCampaign",
|
|
2461
|
+
logCategory: "campaigns.claim"
|
|
2462
|
+
});
|
|
2463
|
+
}
|
|
2464
|
+
getTodayDateString() {
|
|
2465
|
+
const now = /* @__PURE__ */ new Date();
|
|
2466
|
+
const utc8Time = new Date(now.getTime() + (now.getTimezoneOffset() + 480) * 6e4);
|
|
2467
|
+
return `${utc8Time.getFullYear()}-${String(utc8Time.getMonth() + 1).padStart(2, "0")}-${String(utc8Time.getDate()).padStart(2, "0")}`;
|
|
2468
|
+
}
|
|
2469
|
+
async checkIn(pat, signal) {
|
|
2470
|
+
const today = this.getTodayDateString();
|
|
2471
|
+
const nowMs = Date.now();
|
|
2472
|
+
if (!pat || typeof pat !== "string" || pat.trim() === "") return {
|
|
2473
|
+
variantId: this.variantId,
|
|
2474
|
+
date: today,
|
|
2475
|
+
timestamp: nowMs,
|
|
2476
|
+
status: "error",
|
|
2477
|
+
message: "No PAT available"
|
|
2478
|
+
};
|
|
2479
|
+
const executeWithToken = async (authToken) => {
|
|
2480
|
+
const benefitCampaign = (await this.fetchCampaigns(authToken, signal)).find((c) => c.actionType === "CLAIM_BENEFIT");
|
|
2481
|
+
if (!benefitCampaign) return {
|
|
2482
|
+
variantId: this.variantId,
|
|
2483
|
+
date: today,
|
|
2484
|
+
timestamp: nowMs,
|
|
2485
|
+
status: "no-campaign",
|
|
2486
|
+
message: "No claimable benefit campaign found for this region"
|
|
2487
|
+
};
|
|
2488
|
+
if (benefitCampaign.claimStatus === "CLAIMED") return {
|
|
2489
|
+
variantId: this.variantId,
|
|
2490
|
+
date: today,
|
|
2491
|
+
timestamp: nowMs,
|
|
2492
|
+
status: "already-claimed",
|
|
2493
|
+
campaignKey: benefitCampaign.campaignKey,
|
|
2494
|
+
amount: benefitCampaign.benefit?.amount ?? 100,
|
|
2495
|
+
message: "Already claimed today"
|
|
2496
|
+
};
|
|
2497
|
+
const claimResult = await this.claimCampaign(authToken, benefitCampaign.campaignId, signal);
|
|
2498
|
+
const isClaimed = claimResult.status === "CLAIMED";
|
|
2499
|
+
const replayed = Boolean(claimResult.replayed);
|
|
2500
|
+
const amount = claimResult.benefit?.amount ?? benefitCampaign.benefit?.amount ?? 100;
|
|
2501
|
+
if (isClaimed) return {
|
|
2502
|
+
variantId: this.variantId,
|
|
2503
|
+
date: today,
|
|
2504
|
+
timestamp: nowMs,
|
|
2505
|
+
status: replayed ? "already-claimed" : "claimed",
|
|
2506
|
+
amount,
|
|
2507
|
+
campaignKey: benefitCampaign.campaignKey,
|
|
2508
|
+
message: replayed ? "Already claimed today" : `Successfully claimed ${amount} credits`
|
|
2509
|
+
};
|
|
2510
|
+
return {
|
|
2511
|
+
variantId: this.variantId,
|
|
2512
|
+
date: today,
|
|
2513
|
+
timestamp: nowMs,
|
|
2514
|
+
status: "error",
|
|
2515
|
+
campaignKey: benefitCampaign.campaignKey,
|
|
2516
|
+
message: `Claim returned status ${claimResult.status ?? "unknown"}`
|
|
2517
|
+
};
|
|
2518
|
+
};
|
|
2519
|
+
try {
|
|
2520
|
+
const creds = await this.authService.getCredentials(pat, signal);
|
|
2521
|
+
try {
|
|
2522
|
+
return await executeWithToken(creds.authToken);
|
|
2523
|
+
} catch (innerError) {
|
|
2524
|
+
if (isQoderAuthRejection(innerError)) {
|
|
2525
|
+
this.logger?.warn?.(`[Qoder CheckIn] Auth token rejected, retrying with fresh exchange`);
|
|
2526
|
+
return await executeWithToken((await this.authService.exchangeFresh(pat, signal)).authToken);
|
|
2527
|
+
}
|
|
2528
|
+
throw innerError;
|
|
2529
|
+
}
|
|
2530
|
+
} catch (error) {
|
|
2531
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
2532
|
+
this.logger?.error?.(`[Qoder CheckIn] Check-in failed: ${message}`);
|
|
2533
|
+
return {
|
|
2534
|
+
variantId: this.variantId,
|
|
2535
|
+
date: today,
|
|
2536
|
+
timestamp: nowMs,
|
|
2537
|
+
status: "error",
|
|
2538
|
+
message
|
|
2539
|
+
};
|
|
2540
|
+
}
|
|
2541
|
+
}
|
|
2542
|
+
};
|
|
2543
|
+
//#endregion
|
|
2411
2544
|
//#region src/qoder/transport/wire/encoding.ts
|
|
2412
2545
|
/**
|
|
2413
2546
|
* Qoder WAF Body encoding algorithm.
|
|
@@ -2623,7 +2756,10 @@ function parseEnvelope(rawData) {
|
|
|
2623
2756
|
const envelope = value;
|
|
2624
2757
|
if (envelope.statusCodeValue !== void 0 && typeof envelope.statusCodeValue !== "number") throw malformed("Qoder SSE envelope has an invalid status code.");
|
|
2625
2758
|
if (envelope.statusCodeValue !== void 0 && (!Number.isInteger(envelope.statusCodeValue) || envelope.statusCodeValue < 100 || envelope.statusCodeValue > 599)) throw malformed("Qoder SSE envelope has an invalid status code.");
|
|
2626
|
-
if (envelope.statusCodeValue !== void 0 && envelope.statusCodeValue !== 200)
|
|
2759
|
+
if (envelope.statusCodeValue !== void 0 && envelope.statusCodeValue !== 200) {
|
|
2760
|
+
const status = envelope.statusCodeValue;
|
|
2761
|
+
throw qoderHttpError(envelope.body ? `Qoder service returned upstream error status ${status}: ${envelope.body}` : `Qoder service returned upstream error status ${status}.`, { status });
|
|
2762
|
+
}
|
|
2627
2763
|
if (envelope.body !== void 0 && typeof envelope.body !== "string") throw malformed("Qoder SSE envelope has an invalid model body.");
|
|
2628
2764
|
return envelope;
|
|
2629
2765
|
}
|
|
@@ -3030,6 +3166,18 @@ async function* streamQoderChat(options, model, credentials, messages, dependenc
|
|
|
3030
3166
|
requestController.abort("request complete");
|
|
3031
3167
|
}
|
|
3032
3168
|
}
|
|
3169
|
+
/**
|
|
3170
|
+
* How long a rotated-token notice may wait for an accepting chat.
|
|
3171
|
+
*
|
|
3172
|
+
* The heal's own retry can lose a race with a transient fault and be rescued
|
|
3173
|
+
* by a later attempt, so the notice is deferred until some chat is accepted.
|
|
3174
|
+
* That deferral has to be bounded: an unbounded one let a rotation from
|
|
3175
|
+
* minutes earlier surface as a fresh-looking row whose timestamp named a
|
|
3176
|
+
* moment the user could not connect to the row appearing now. Five minutes
|
|
3177
|
+
* comfortably covers the host's own retry cycle (its backoff caps at 10s)
|
|
3178
|
+
* while keeping the row adjacent to the event it describes.
|
|
3179
|
+
*/
|
|
3180
|
+
const jobTokenNoticeMaxAgeMs = 3e5;
|
|
3033
3181
|
function aborted(message) {
|
|
3034
3182
|
return new QoderLlmError(message, "ABORTED");
|
|
3035
3183
|
}
|
|
@@ -3043,11 +3191,13 @@ var DefaultQoderTransport = class {
|
|
|
3043
3191
|
metadataTimeoutMs;
|
|
3044
3192
|
auth;
|
|
3045
3193
|
usage;
|
|
3194
|
+
checkInService;
|
|
3046
3195
|
modelFlights = new SingleFlight();
|
|
3047
3196
|
attachments;
|
|
3048
3197
|
imageUploader;
|
|
3049
3198
|
preserveThinking;
|
|
3050
3199
|
onJobTokenRefreshed;
|
|
3200
|
+
onJobTokenRefreshFailed;
|
|
3051
3201
|
/**
|
|
3052
3202
|
* Set when the self-heal exchanged a fresh job token, cleared when a chat
|
|
3053
3203
|
* afterwards succeeds. The notice reports "the token was rotated because the
|
|
@@ -3056,6 +3206,16 @@ var DefaultQoderTransport = class {
|
|
|
3056
3206
|
* transient upstream timeout and be rescued by a later attempt.
|
|
3057
3207
|
*/
|
|
3058
3208
|
pendingRefreshAt;
|
|
3209
|
+
/**
|
|
3210
|
+
* Whether the current unresolved heal failure has already been reported.
|
|
3211
|
+
*
|
|
3212
|
+
* One upstream rejection can outlive many host-level retries (an observed
|
|
3213
|
+
* storm ran 59 of them across 75 steps), and the transport re-heals inside
|
|
3214
|
+
* every one of them. Without this latch the failure notice would print once
|
|
3215
|
+
* per retry; with it, the user is told once per outage. Cleared as soon as
|
|
3216
|
+
* any chat is accepted, so the next outage announces itself again.
|
|
3217
|
+
*/
|
|
3218
|
+
refreshFailureAnnounced = false;
|
|
3059
3219
|
constructor(options) {
|
|
3060
3220
|
this.region = options.region;
|
|
3061
3221
|
this.resolvePat = options.resolvePat;
|
|
@@ -3067,6 +3227,7 @@ var DefaultQoderTransport = class {
|
|
|
3067
3227
|
this.attachments = options.attachments;
|
|
3068
3228
|
this.preserveThinking = options.preserveThinking;
|
|
3069
3229
|
this.onJobTokenRefreshed = options.onJobTokenRefreshed;
|
|
3230
|
+
this.onJobTokenRefreshFailed = options.onJobTokenRefreshFailed;
|
|
3070
3231
|
this.auth = new QoderAuthService({
|
|
3071
3232
|
fetch: this.fetchImpl,
|
|
3072
3233
|
logger: this.logger,
|
|
@@ -3092,6 +3253,13 @@ var DefaultQoderTransport = class {
|
|
|
3092
3253
|
region: this.region,
|
|
3093
3254
|
timeoutMs: this.metadataTimeoutMs
|
|
3094
3255
|
});
|
|
3256
|
+
this.checkInService = new QoderCheckInService({
|
|
3257
|
+
authService: this.auth,
|
|
3258
|
+
fetch: this.fetchImpl,
|
|
3259
|
+
logger: this.logger,
|
|
3260
|
+
region: this.region,
|
|
3261
|
+
timeoutMs: this.metadataTimeoutMs
|
|
3262
|
+
});
|
|
3095
3263
|
}
|
|
3096
3264
|
stream(options, model) {
|
|
3097
3265
|
return this.generate(options, model);
|
|
@@ -3107,12 +3275,35 @@ var DefaultQoderTransport = class {
|
|
|
3107
3275
|
if (this.pendingRefreshAt === void 0) return;
|
|
3108
3276
|
const at = this.pendingRefreshAt;
|
|
3109
3277
|
this.pendingRefreshAt = void 0;
|
|
3278
|
+
if (Date.now() - at > jobTokenNoticeMaxAgeMs) return;
|
|
3110
3279
|
this.logger?.warn?.("[Qoder Stream] Job token was auto-refreshed after an upstream rejection; the chat has recovered");
|
|
3111
3280
|
this.onJobTokenRefreshed?.({
|
|
3112
3281
|
region: this.region,
|
|
3113
3282
|
at
|
|
3114
3283
|
});
|
|
3115
3284
|
}
|
|
3285
|
+
/**
|
|
3286
|
+
* Drop a pending rotation notice whose heal did not rescue anything.
|
|
3287
|
+
*
|
|
3288
|
+
* The heal's own retry was rejected too, so "the rotation fixed it" is not
|
|
3289
|
+
* what happened. Leaving the notice pending made a LATER, unrelated chat
|
|
3290
|
+
* acceptance flush it — printing a success row that contradicts the failure
|
|
3291
|
+
* row already shown, stamped with the old rotation time.
|
|
3292
|
+
*/
|
|
3293
|
+
discardPendingRefreshNotice() {
|
|
3294
|
+
this.pendingRefreshAt = void 0;
|
|
3295
|
+
}
|
|
3296
|
+
/**
|
|
3297
|
+
* Note that the upstream accepted a chat, ending any unresolved heal failure.
|
|
3298
|
+
*
|
|
3299
|
+
* `streamChat` calls this the moment its first chunk arrives — the only point
|
|
3300
|
+
* at which "the credential was accepted" is actually known. A rejected chat
|
|
3301
|
+
* throws before that, so this never fires on a failing attempt.
|
|
3302
|
+
*/
|
|
3303
|
+
onChatAccepted() {
|
|
3304
|
+
this.refreshFailureAnnounced = false;
|
|
3305
|
+
this.flushPendingRefreshNotice();
|
|
3306
|
+
}
|
|
3116
3307
|
async discoverModels(signal) {
|
|
3117
3308
|
const pat = await this.requirePat(signal);
|
|
3118
3309
|
const key = opaqueCredentialKey(pat);
|
|
@@ -3134,6 +3325,10 @@ var DefaultQoderTransport = class {
|
|
|
3134
3325
|
signal: options?.signal
|
|
3135
3326
|
});
|
|
3136
3327
|
}
|
|
3328
|
+
async checkIn(signal) {
|
|
3329
|
+
const pat = await this.requirePat(signal);
|
|
3330
|
+
return this.checkInService.checkIn(pat, signal);
|
|
3331
|
+
}
|
|
3137
3332
|
async requirePat(signal) {
|
|
3138
3333
|
if (signal?.aborted) throw aborted("Qoder request was aborted.");
|
|
3139
3334
|
const pat = (await this.resolvePat()).trim();
|
|
@@ -3178,7 +3373,7 @@ var DefaultQoderTransport = class {
|
|
|
3178
3373
|
const chatCredentials = await this.auth.getCredentials(pat, options.signal);
|
|
3179
3374
|
try {
|
|
3180
3375
|
yield* this.streamChat(options, model, chatCredentials, messages, () => {
|
|
3181
|
-
this.
|
|
3376
|
+
this.onChatAccepted();
|
|
3182
3377
|
});
|
|
3183
3378
|
return;
|
|
3184
3379
|
} catch (error) {
|
|
@@ -3191,7 +3386,7 @@ var DefaultQoderTransport = class {
|
|
|
3191
3386
|
this.pendingRefreshAt = Date.now();
|
|
3192
3387
|
try {
|
|
3193
3388
|
yield* this.streamChat(options, model, refreshed, messages, () => {
|
|
3194
|
-
this.
|
|
3389
|
+
this.onChatAccepted();
|
|
3195
3390
|
});
|
|
3196
3391
|
return;
|
|
3197
3392
|
} catch (error) {
|
|
@@ -3212,6 +3407,16 @@ var DefaultQoderTransport = class {
|
|
|
3212
3407
|
}
|
|
3213
3408
|
}
|
|
3214
3409
|
}
|
|
3410
|
+
this.discardPendingRefreshNotice();
|
|
3411
|
+
if (!this.refreshFailureAnnounced) {
|
|
3412
|
+
this.refreshFailureAnnounced = true;
|
|
3413
|
+
this.logger?.warn?.("[Qoder Stream] Job token refresh did not recover the chat; the upstream still rejects it", { status: lastRejection instanceof LlmError ? lastRejection.failure.status : void 0 });
|
|
3414
|
+
this.onJobTokenRefreshFailed?.({
|
|
3415
|
+
region: this.region,
|
|
3416
|
+
at: Date.now(),
|
|
3417
|
+
...lastRejection instanceof LlmError && lastRejection.failure.status !== void 0 ? { status: lastRejection.failure.status } : {}
|
|
3418
|
+
});
|
|
3419
|
+
}
|
|
3215
3420
|
throw lastRejection;
|
|
3216
3421
|
}
|
|
3217
3422
|
};
|
|
@@ -3618,14 +3823,23 @@ var ChunkEncoder = class {
|
|
|
3618
3823
|
}] })];
|
|
3619
3824
|
}
|
|
3620
3825
|
case "usage": {
|
|
3621
|
-
const
|
|
3826
|
+
const cacheRead = chunk.usage.cacheReadTokens;
|
|
3827
|
+
const cacheWrite = chunk.usage.cacheWriteTokens;
|
|
3828
|
+
const prompt = chunk.usage.inputTokens + (cacheRead ?? 0) + (cacheWrite ?? 0);
|
|
3622
3829
|
const completion = chunk.usage.outputTokens;
|
|
3830
|
+
const reasoning = chunk.usage.reasoningTokens;
|
|
3831
|
+
const details = cacheRead === void 0 && cacheWrite === void 0 ? void 0 : {
|
|
3832
|
+
...cacheRead === void 0 ? {} : { cached_tokens: cacheRead },
|
|
3833
|
+
...cacheWrite === void 0 ? {} : { cache_write_tokens: cacheWrite }
|
|
3834
|
+
};
|
|
3623
3835
|
return [this.frame({
|
|
3624
3836
|
choices: [],
|
|
3625
3837
|
usage: {
|
|
3626
3838
|
prompt_tokens: prompt,
|
|
3627
3839
|
completion_tokens: completion,
|
|
3628
|
-
total_tokens: chunk.usage.totalTokens ?? prompt + completion
|
|
3840
|
+
total_tokens: chunk.usage.totalTokens ?? prompt + completion,
|
|
3841
|
+
...details === void 0 ? {} : { prompt_tokens_details: details },
|
|
3842
|
+
...reasoning === void 0 ? {} : { completion_tokens_details: { reasoning_tokens: reasoning } }
|
|
3629
3843
|
}
|
|
3630
3844
|
})];
|
|
3631
3845
|
}
|
|
@@ -3779,6 +3993,12 @@ var QoderUpstreamClient = class {
|
|
|
3779
3993
|
};
|
|
3780
3994
|
}
|
|
3781
3995
|
/**
|
|
3996
|
+
* Run daily benefit check-in for this client's variant.
|
|
3997
|
+
*/
|
|
3998
|
+
async checkIn(signal) {
|
|
3999
|
+
return this.transport.checkIn(signal);
|
|
4000
|
+
}
|
|
4001
|
+
/**
|
|
3782
4002
|
* Stream one translated chat-completions request.
|
|
3783
4003
|
*
|
|
3784
4004
|
* The first transport chunk is pulled before the response is constructed:
|
|
@@ -4013,7 +4233,7 @@ const DEFAULT_MAX_TOKENS = 32768;
|
|
|
4013
4233
|
const DEFAULT_CONTEXT_WINDOW = 18e4;
|
|
4014
4234
|
//#endregion
|
|
4015
4235
|
//#region src/version.ts
|
|
4016
|
-
const QODER_CONNECT_VERSION = "0.1.
|
|
4236
|
+
const QODER_CONNECT_VERSION = "0.1.5";
|
|
4017
4237
|
//#endregion
|
|
4018
4238
|
//#region src/host-heartbeat.ts
|
|
4019
4239
|
/**
|
package/package.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"name": "dsh-qoder-connect",
|
|
3
3
|
"displayName": "DSH Qoder Connect",
|
|
4
4
|
"description": "将 Qoder(国内版 / 国际版)的模型接入 DeepSeek Harness — bring Qoder (CN & international) models into DeepSeek Harness.",
|
|
5
|
-
"version": "0.1.
|
|
5
|
+
"version": "0.1.5",
|
|
6
6
|
"author": "corrinehu",
|
|
7
7
|
"keywords": [
|
|
8
8
|
"dsh-plugin",
|
|
@@ -23,7 +23,7 @@
|
|
|
23
23
|
"scripts": {
|
|
24
24
|
"prepack": "npm run build",
|
|
25
25
|
"build": "tsdown",
|
|
26
|
-
"test": "vitest run",
|
|
26
|
+
"test": "node -r ./tests/sandbox-shim.cjs ./node_modules/vitest/vitest.mjs run",
|
|
27
27
|
"test:qoder": "node --test --test-isolation=none --experimental-strip-types tests/qoder/*.test.ts",
|
|
28
28
|
"typecheck": "tsc -p tsconfig.json && tsc -p tsconfig.client.json",
|
|
29
29
|
"check": "pnpm run typecheck && pnpm run test && pnpm run test:qoder && pnpm run build"
|