dsh-provider-qoder 0.4.3 → 0.5.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/lib/index.js CHANGED
@@ -1,12 +1,18 @@
1
1
  import z from "@deepseek-ai/schemastery";
2
2
  import { CONTEXT_WINDOW_EXCEEDED_CODE, EMPTY_RESPONSE_CODE, LlmAdapter, LlmError, ProviderRequestId, QUOTA_EXCEEDED_CODE, ReasoningEffortId, ToolCallId, attributionHeaders, contentHasImage, isContextWindowExceededError, isQuotaExceededError } from "@deepseek-ai/dsh-llm";
3
- import crypto, { createHash } from "node:crypto";
3
+ import crypto, { createHash, randomBytes, randomUUID } from "node:crypto";
4
4
  import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
5
5
  import { homedir, hostname } from "node:os";
6
6
  import { dirname, join, resolve } from "node:path";
7
7
  import { WebError } from "@deepseek-ai/dsh-web";
8
+ import * as zlib from "node:zlib";
9
+ import { promisify } from "node:util";
10
+ import { setTimeout as setTimeout$1 } from "node:timers/promises";
8
11
  import { SettingsConflictError } from "@deepseek-ai/dsh-settings";
9
- import { credentialRef } from "@deepseek-ai/dsh-credentials";
12
+ import { credentialKey, credentialRef } from "@deepseek-ai/dsh-credentials";
13
+ //#region src/qoder/queue.ts
14
+ const defaultModelQueueMaxWaitMs = 3600 * 1e3;
15
+ //#endregion
10
16
  //#region src/qoder/catalog.ts
11
17
  const discoveredMetadataKeys = [
12
18
  "description",
@@ -103,7 +109,7 @@ function normalizeQoderModels(payload, onConflict) {
103
109
  const rawOriginalPrice = nonNegativeNumber(raw.original_price_factor) ?? nonNegativeNumber(raw.originPriceFactor);
104
110
  const promotionPrice = typeof raw.promotion === "object" && raw.promotion !== null && raw.promotion.active === true ? nonNegativeNumber(raw.promotion.before_promotion_price_factor) : void 0;
105
111
  const originalPriceFactor = rawOriginalPrice ?? promotionPrice;
106
- const isFree = raw.is_free === true || raw.isFree === true || Array.isArray(raw.tags) && raw.tags.includes("limited_time_free") || priceFactor === 0;
112
+ const isFree = priceFactor === void 0 ? raw.is_free === true || raw.isFree === true || Array.isArray(raw.tags) && raw.tags.includes("limited_time_free") : priceFactor === 0;
107
113
  const reasoning = reasoningEffortsOf(raw.thinking_config);
108
114
  models.push({
109
115
  id,
@@ -153,12 +159,12 @@ function formatRateFactor(value) {
153
159
  }
154
160
  /**
155
161
  * Format model price factor / rate string.
156
- * - Free models or price factor 0: '免费' or 'Free'
162
+ * - Price factor 0, or free models without a price factor: '免费' or 'Free'
157
163
  * - Promotional models: '0.5x(原 1x)' or '0.5x (was 1x)'
158
164
  * - Standard models: '1x'
159
165
  */
160
166
  function formatModelRate(model, isZh = true) {
161
- if (model.isFree || model.priceFactor === 0) return isZh ? "免费" : "Free";
167
+ if (model.priceFactor === 0 || model.priceFactor === void 0 && model.isFree) return isZh ? "免费" : "Free";
162
168
  if (model.priceFactor === void 0) return void 0;
163
169
  const current = `${formatRateFactor(model.priceFactor)}x`;
164
170
  if (model.originalPriceFactor !== void 0 && model.originalPriceFactor > model.priceFactor) {
@@ -305,6 +311,21 @@ function getQoderChatUrl(region = "global") {
305
311
  const { baseUrl } = resolveQoderEndpoints(region);
306
312
  return `${baseUrl}algo/api/v2/service/pro/sse/agent_chat_generation?FetchKeys=llm_model_result&AgentId=agent_common&Encode=1`;
307
313
  }
314
+ function getQoderQueueStatusUrl(region, params) {
315
+ const query = new URLSearchParams({
316
+ requestSetId: params.requestSetId,
317
+ modelKey: params.modelKey
318
+ });
319
+ if (params.queueType) query.set("queueType", params.queueType);
320
+ return `${resolveQoderEndpoints(region).baseUrl}algo/api/v2/service/ask/queue/status?${query}`;
321
+ }
322
+ function getQoderQueueFinishUrl(region) {
323
+ return `${resolveQoderEndpoints(region).baseUrl}algo/api/v2/service/ask/finish?Encode=1`;
324
+ }
325
+ /** model-server HTTP endpoint. */
326
+ function getQoderModelServerUrl(region = "global") {
327
+ return `https://${region === "china" ? "gateway.qoder.com.cn" : "api2-v2.qoder.sh"}/model/v1/chat/completions`;
328
+ }
308
329
  function getQoderModelListUrl(region = "global") {
309
330
  const { baseUrl } = resolveQoderEndpoints(region);
310
331
  return `${baseUrl}algo/api/v2/model/list?Encode=1`;
@@ -355,6 +376,15 @@ var QoderLlmError = class extends LlmError {
355
376
  super(message, code, options);
356
377
  }
357
378
  };
379
+ /** Internal protocol signal; the queue controller consumes it before DSH sees it. */
380
+ var QoderModelQueuedError = class extends QoderLlmError {
381
+ queue;
382
+ constructor(queue) {
383
+ super("Qoder model is queued.", "QODER_MODEL_QUEUED");
384
+ this.queue = queue;
385
+ this.upstreamCode = "10605";
386
+ }
387
+ };
358
388
  const modelBusinessErrors = {
359
389
  "103": {
360
390
  code: "PROVIDER_ERROR",
@@ -519,6 +549,42 @@ function qoderModelError(message, response) {
519
549
  function canRefreshModelCredentials(error) {
520
550
  return error.source === "http" && (error.failure.status === 401 || error.failure.status === 403) && error.code === "AUTH" && (error.upstreamCode === void 0 || error.upstreamCode === "105");
521
551
  }
552
+ /** Model-server errors have their own wire contract; do not apply legacy business codes. */
553
+ function qoderModelServerError(status, cause, source, headers) {
554
+ let payload = cause;
555
+ if (typeof payload === "string") try {
556
+ payload = JSON.parse(payload);
557
+ } catch {}
558
+ const root = payload !== null && typeof payload === "object" ? payload : {};
559
+ const nested = root.error !== null && typeof root.error === "object" ? root.error : root;
560
+ const rawCode = nested.code ?? root.code;
561
+ const upstreamCode = typeof rawCode === "string" || typeof rawCode === "number" ? String(rawCode) : void 0;
562
+ const effectiveStatus = upstreamCode === "105" ? 401 : status;
563
+ const fallback = qoderHttpError(`Qoder model-server returned HTTP ${status}.`, {
564
+ status: effectiveStatus,
565
+ headers
566
+ });
567
+ const code = upstreamCode === "105" ? "AUTH" : upstreamCode === "10605" ? "RATE_LIMIT" : upstreamCode && (/* @__PURE__ */ new Set([
568
+ "110",
569
+ "112",
570
+ "114",
571
+ "115",
572
+ "116",
573
+ "117",
574
+ "118",
575
+ "119",
576
+ "122"
577
+ ])).has(upstreamCode) ? QUOTA_EXCEEDED_CODE : upstreamCode === "430" ? "INVALID_REQUEST" : upstreamCode === void 0 ? fallback.code : "PROVIDER_ERROR";
578
+ const error = new QoderLlmError(typeof nested.message === "string" ? nested.message.slice(0, 300) : fallback.message, code, {
579
+ status: effectiveStatus,
580
+ ...fallback.failure.requestId === void 0 ? {} : { requestId: fallback.failure.requestId },
581
+ ...fallback.failure.providerRetryAfterMs === void 0 ? {} : { providerRetryAfterMs: fallback.failure.providerRetryAfterMs }
582
+ });
583
+ error.source = source;
584
+ error.httpStatus = source === "http" ? status : void 0;
585
+ error.upstreamCode = upstreamCode;
586
+ return error;
587
+ }
522
588
  function qoderHttpError(message, response) {
523
589
  const { status } = response;
524
590
  const code = status === 401 || status === 403 ? "AUTH" : status === 408 ? "TIMEOUT" : status === 429 ? "RATE_LIMIT" : status >= 500 && status <= 599 ? "SERVER" : status >= 400 && status <= 499 ? "INVALID_REQUEST" : "PROVIDER_ERROR";
@@ -650,6 +716,24 @@ function logParsedResponse(logger, operation, result) {
650
716
  }));
651
717
  }
652
718
  //#endregion
719
+ //#region src/qoder/transport/wire/client-identity.ts
720
+ /** Client identity shared by Qoder model protocols and Open API exchanges. */
721
+ const qoderIdeVersion = "1.1.73";
722
+ const defaultUserAgent = `qoder/${qoderIdeVersion}`;
723
+ function qoderMachineOs(platform = process.platform, arch = process.arch) {
724
+ return `${arch === "arm64" ? "aarch64" : arch === "x64" ? "x86_64" : arch}_${platform}`;
725
+ }
726
+ /** Identity injected by qodercli's common infer-sse HTTP request pipeline. */
727
+ function qoderModelClientHeaders() {
728
+ const machineHostname = hostname().replace(/[^\x20-\x7e]/gu, "").trim();
729
+ return {
730
+ "Cosy-Version": qoderIdeVersion,
731
+ "Cosy-ClientType": "5",
732
+ "Cosy-MachineOS": qoderMachineOs(),
733
+ ...machineHostname ? { "Cosy-MachineHostname": machineHostname } : {}
734
+ };
735
+ }
736
+ //#endregion
653
737
  //#region src/qoder/transport/wire/cosy.ts
654
738
  /**
655
739
  * COSY authentication headers and signature algorithm for Qoder services.
@@ -665,14 +749,9 @@ MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDA8iMH5c02LilrsERw9t6Pv5Nc
665
749
  6HRkPJ7S236FZz73In/KVuLnwI8JJ2CbuJap8kvheCCZpmAWpb/cPx/3Vr/J6I17
666
750
  XcW+ML9FoCI6AOvOzwIDAQAB
667
751
  -----END PUBLIC KEY-----`;
668
- const qoderIdeVersion = "1.1.65";
669
- const defaultUserAgent = `qoder/${qoderIdeVersion}`;
670
752
  const qoderDataPolicy = "disagree";
671
753
  const qoderLoginVersion = "v2";
672
754
  const qoderMachineTypeMagic = "5";
673
- function qoderMachineOs(platform = process.platform, arch = process.arch) {
674
- return `${arch === "arm64" ? "aarch64" : arch === "x64" ? "x86_64" : arch}_${platform}`;
675
- }
676
755
  function computeSigPath(urlStr) {
677
756
  let sigPath = new URL(urlStr).pathname;
678
757
  if (sigPath.startsWith("/algo")) sigPath = sigPath.substring(5);
@@ -938,9 +1017,11 @@ async function waitForFlight(promise, signal) {
938
1017
  if (signal.aborted) onAbort();
939
1018
  });
940
1019
  }
941
- var QoderAuthService = class {
1020
+ var QoderAuthManager = class {
942
1021
  cache = /* @__PURE__ */ new Map();
943
1022
  inFlight = /* @__PURE__ */ new Map();
1023
+ epochs = /* @__PURE__ */ new Map();
1024
+ nextVersion = 0;
944
1025
  fetchImpl;
945
1026
  timeoutMs;
946
1027
  organizationTagsTimeoutMs;
@@ -956,15 +1037,39 @@ var QoderAuthService = class {
956
1037
  this.logger = options.logger;
957
1038
  }
958
1039
  clear(pat) {
959
- if (pat) this.cache.delete(`${this.region}:${opaqueCredentialKey(pat)}`);
960
- else this.cache.clear();
1040
+ if (pat) this.invalidate(`${this.region}:${opaqueCredentialKey(pat)}`);
1041
+ else for (const key of /* @__PURE__ */ new Set([...this.cache.keys(), ...this.inFlight.keys()])) this.invalidate(key);
1042
+ }
1043
+ invalidate(key) {
1044
+ this.epochs.set(key, (this.epochs.get(key) ?? 0) + 1);
1045
+ this.cache.delete(key);
1046
+ const flight = this.inFlight.get(key);
1047
+ if (flight) {
1048
+ this.inFlight.delete(key);
1049
+ flight.controller.abort("credential invalidated");
1050
+ }
1051
+ }
1052
+ /** Recover a rejected credential once; concurrent rejections share the exchange. */
1053
+ async refreshCredentials(pat, rejected, signal) {
1054
+ if (signal?.aborted) throw abortedError();
1055
+ const key = `${this.region}:${opaqueCredentialKey(pat)}`;
1056
+ const current = this.cache.get(key)?.creds;
1057
+ if (current && current !== rejected) return current;
1058
+ this.cache.delete(key);
1059
+ if (this.inFlight.get(key)) return this.getCredentials(pat, signal);
1060
+ this.invalidate(key);
1061
+ return this.getCredentials(pat, signal);
961
1062
  }
962
1063
  async getCredentials(pat, signal) {
963
1064
  if (!pat || typeof pat !== "string") throw new QoderLlmError("Qoder Personal Access Token is missing or invalid. Configure Qoder in the Qoder settings page.", "MISSING_CREDENTIAL");
964
1065
  if (signal?.aborted) throw abortedError();
965
1066
  const cacheKey = `${this.region}:${opaqueCredentialKey(pat)}`;
966
1067
  const cached = this.cache.get(cacheKey);
967
- if (cached && cached.expiresAt > Date.now() + expiryBufferMs) return cached.creds;
1068
+ if (cached) {
1069
+ const lifetime = cached.expiresAt - cached.issuedAt;
1070
+ const buffer = Math.min(expiryBufferMs, Math.max(0, lifetime / 5));
1071
+ if (cached.expiresAt > Date.now() + buffer) return cached.creds;
1072
+ }
968
1073
  let entry = this.inFlight.get(cacheKey);
969
1074
  if (entry === void 0 || entry.controller.signal.aborted) {
970
1075
  const controller = new AbortController();
@@ -973,7 +1078,12 @@ var QoderAuthService = class {
973
1078
  created.waiters = 0;
974
1079
  created.settled = false;
975
1080
  created.timeout = setTimeout(() => controller.abort("authentication timeout"), this.timeoutMs);
976
- created.promise = this.exchangeAndResolve(pat, controller.signal, () => clearTimeout(created.timeout)).finally(() => {
1081
+ const epoch = this.epochs.get(cacheKey) ?? 0;
1082
+ created.promise = this.exchangeAndResolve(pat, controller.signal, () => clearTimeout(created.timeout), epoch).catch((error) => {
1083
+ const stale = this.cache.get(cacheKey);
1084
+ if (error instanceof QoderLlmError && (error.code === "TRANSPORT" || error.code === "TIMEOUT" || error.code === "SERVER") && stale && stale.expiresAt > Date.now() && (this.epochs.get(cacheKey) ?? 0) === epoch && !controller.signal.aborted) return stale.creds;
1085
+ throw error;
1086
+ }).finally(() => {
977
1087
  created.settled = true;
978
1088
  clearTimeout(created.timeout);
979
1089
  if (this.inFlight.get(cacheKey) === created) this.inFlight.delete(cacheKey);
@@ -992,7 +1102,7 @@ var QoderAuthService = class {
992
1102
  }
993
1103
  }
994
1104
  }
995
- async exchangeAndResolve(pat, signal, onAuthenticated) {
1105
+ async exchangeAndResolve(pat, signal, onAuthenticated, epoch) {
996
1106
  const machineID = this.resolveMachineId().trim();
997
1107
  const data = await openApiJsonRequest(this.fetchImpl, {
998
1108
  url: getQoderExchangeUrl(this.region),
@@ -1013,6 +1123,22 @@ var QoderAuthService = class {
1013
1123
  ].find((value) => typeof value === "string" && value.length > 0);
1014
1124
  if (!jobToken) throw new QoderLlmError("Qoder PAT exchange returned no job token.", "AUTH");
1015
1125
  const expiresAt = jobTokenExpiry(data);
1126
+ const creds = {
1127
+ ...await this.credentialsForToken(jobToken, machineID, signal, onAuthenticated),
1128
+ expiresAt,
1129
+ version: ++this.nextVersion
1130
+ };
1131
+ const cacheKey = `${this.region}:${opaqueCredentialKey(pat)}`;
1132
+ if ((this.epochs.get(cacheKey) ?? 0) !== epoch || signal.aborted) throw abortedError();
1133
+ this.cache.set(cacheKey, {
1134
+ creds,
1135
+ expiresAt,
1136
+ issuedAt: Date.now()
1137
+ });
1138
+ return creds;
1139
+ }
1140
+ /** Both PAT exchange and browser authorization use the same verified subscriber identity. */
1141
+ async credentialsForToken(jobToken, machineID, signal, onAuthenticated = () => {}) {
1016
1142
  const userInfo = await retryMetadataRead(signal, () => this.fetchUserInfo(jobToken, signal));
1017
1143
  if (signal.aborted) throw abortedError();
1018
1144
  onAuthenticated();
@@ -1031,11 +1157,6 @@ var QoderAuthService = class {
1031
1157
  if (tags !== void 0) creds.organizationTags = tags;
1032
1158
  }
1033
1159
  if (signal.aborted) throw abortedError();
1034
- const cacheKey = `${this.region}:${opaqueCredentialKey(pat)}`;
1035
- this.cache.set(cacheKey, {
1036
- creds,
1037
- expiresAt
1038
- });
1039
1160
  return creds;
1040
1161
  }
1041
1162
  async fetchUserInfo(jobToken, signal) {
@@ -1613,8 +1734,8 @@ var QoderTurnTracker = class {
1613
1734
  }
1614
1735
  };
1615
1736
  //#endregion
1616
- //#region src/qoder/transport/wire/serialize.ts
1617
- /** Build the minimum qodercli request envelope for a validated DSH request. */
1737
+ //#region src/qoder/transport/wire/prepare.ts
1738
+ /** Shared model request preparation, before selecting a wire protocol. */
1618
1739
  function stableHash(prefix, ...inputs) {
1619
1740
  const hash = crypto.createHash("sha256");
1620
1741
  hash.update(prefix);
@@ -1624,33 +1745,11 @@ function stableHash(prefix, ...inputs) {
1624
1745
  }
1625
1746
  return hash.digest("hex").slice(0, 16);
1626
1747
  }
1627
- /**
1628
- * Turn identity shared by every request the process serves.
1629
- *
1630
- * Qoder's Credits panel aggregates consumption per agent run — the
1631
- * `business.id` a request reports — so every step of one turn must carry the
1632
- * same run identity while the next subscriber prompt opens a new one. The
1633
- * tracker owns that boundary.
1634
- */
1748
+ /** One tracker preserves agent-run identity across both model protocols. */
1635
1749
  const qoderTurnTracker = new QoderTurnTracker();
1636
- /**
1637
- * Whether this request serves the subscriber's turn or the host's bookkeeping.
1638
- *
1639
- * The host marks an auxiliary call through `GenerateOptions.purpose`, and those
1640
- * calls carry a message list of their own under the same session identity. They
1641
- * must never move the open turn's boundary, or one subscriber prompt would
1642
- * report two consumption records.
1643
- */
1644
1750
  function callKind(options) {
1645
1751
  return options.purpose === void 0 ? "conversation" : "auxiliary";
1646
1752
  }
1647
- /**
1648
- * Reject an unusable request before any credential resolution or provider I/O.
1649
- *
1650
- * Image publication needs credentials, so message translation now runs after
1651
- * authentication. This static pass preserves the guarantee that a request the
1652
- * provider cannot serve never reaches the network.
1653
- */
1654
1753
  function validateQoderRequestShape(options, model) {
1655
1754
  if (options.reasoningEffort !== void 0) {
1656
1755
  const effort = String(options.reasoningEffort);
@@ -1658,29 +1757,24 @@ function validateQoderRequestShape(options, model) {
1658
1757
  }
1659
1758
  if (options.messages.some((message) => contentHasImage(message.content)) && model?.supportsImages !== true) throw new QoderLlmError(`Qoder model "${options.model}" does not advertise image input.`, "UNSUPPORTED_CONTENT");
1660
1759
  }
1661
- /** Translate a request whose shape has already been validated. */
1662
1760
  function translateQoderMessages(options, attachments, pipeline) {
1663
1761
  return validateAndTranslateMessages(options.messages, options.system, attachments, options.signal, pipeline);
1664
1762
  }
1665
- async function validateQoderRequest(options, model, attachments) {
1666
- validateQoderRequestShape(options, model);
1667
- return validateAndTranslateMessages(options.messages, options.system, attachments, options.signal);
1668
- }
1669
- async function buildQoderRequestBody(options, userId, translatedMessages, model, attachments, tracker) {
1763
+ async function prepareQoderRequest(options, userId, translatedMessages, model, attachments, tracker = qoderTurnTracker, identityScope = "") {
1670
1764
  if (!userId) throw new QoderLlmError("Qoder request identity is missing.", "AUTH");
1765
+ if (translatedMessages === void 0) validateQoderRequestShape(options, model);
1766
+ const messages = translatedMessages ?? await translateQoderMessages(options, attachments);
1671
1767
  const modelKey = options.model || "cmodel";
1672
- const messages = translatedMessages ?? await validateQoderRequest(options, model, attachments);
1673
1768
  const modelMaxTokens = model?.maxTokens ?? 32768;
1674
1769
  const maxTokens = Math.min(options.maxTokens ?? modelMaxTokens, modelMaxTokens);
1675
1770
  const isReasoning = options.reasoningEffort !== void 0 || (model?.isReasoning ?? false);
1676
- const tools = translateTools(options.tools);
1677
1771
  const selectedTier = model === void 0 ? void 0 : selectedContextTier(model);
1678
1772
  const defaultContexts = Object.values(model?.contextOptions ?? {}).filter((option) => option.isDefault === true && typeof option.tokenCount === "number" && Number.isFinite(option.tokenCount) && option.tokenCount > 0);
1679
1773
  const contextConfig = model?.contextOptions === void 0 || selectedTier === void 0 && defaultContexts.length !== 1 ? void 0 : Object.fromEntries(Object.entries(model.contextOptions).map(([key, value]) => [key, {
1680
1774
  ...value.tokenCount === void 0 ? {} : { token_count: value.tokenCount },
1681
1775
  ...selectedTier === void 0 ? value.isDefault === void 0 ? {} : { is_default: value.isDefault } : { is_default: key === selectedTier.key }
1682
1776
  }]));
1683
- const effectiveBudget = selectedTier?.tokenCount ?? model?.contextWindow;
1777
+ const contextLength = selectedTier?.tokenCount ?? model?.contextWindow;
1684
1778
  let lastUserText = "";
1685
1779
  for (let index = messages.length - 1; index >= 0; index--) if (messages[index].role === "user") {
1686
1780
  lastUserText = wireMessageText(messages[index]);
@@ -1690,57 +1784,27 @@ async function buildQoderRequestBody(options, userId, translatedMessages, model,
1690
1784
  const sessionId = options.sessionId === void 0 ? `${stablePart}-${crypto.randomUUID()}` : `${stablePart}-${String(options.sessionId)}`;
1691
1785
  const requestId = crypto.randomUUID();
1692
1786
  const sessionIdParam = options.sessionId === void 0 ? void 0 : String(options.sessionId);
1693
- const identity = (tracker ?? qoderTurnTracker).resolveTurnIdentity(sessionIdParam, messages, callKind(options), requestId, modelKey);
1787
+ const identity = tracker.resolveTurnIdentity(sessionIdParam === void 0 ? void 0 : JSON.stringify([
1788
+ identityScope,
1789
+ userId,
1790
+ sessionIdParam
1791
+ ]), messages, callKind(options), requestId, modelKey);
1694
1792
  return {
1695
- request_id: requestId,
1696
- request_set_id: identity.requestSetId,
1697
- chat_record_id: identity.chatRecordId,
1698
- session_id: sessionId,
1699
- stream: true,
1700
- chat_task: "FREE_INPUT",
1701
- is_reply: true,
1702
- is_retry: false,
1703
- source: 1,
1704
- version: "3",
1705
- session_type: "qodercli",
1706
- agent_id: "agent_common",
1707
- task_id: "common",
1708
- code_language: "",
1709
- chat_prompt: "",
1710
- image_urls: null,
1711
- aliyun_user_type: "",
1712
- system: messages.filter((message) => message.role === "system").flatMap((message) => typeof message.content === "string" && message.content.length > 0 ? [{
1713
- type: "text",
1714
- text: message.content
1715
- }] : []),
1793
+ requestId,
1794
+ requestSetId: identity.requestSetId,
1795
+ chatRecordId: identity.chatRecordId,
1796
+ sessionId,
1797
+ taskId: "common",
1798
+ modelKey,
1799
+ modelSource: model?.source || "system",
1716
1800
  messages,
1717
- tools,
1718
- parameters: {
1719
- max_tokens: maxTokens,
1720
- ...effectiveBudget === void 0 ? {} : { context_length: effectiveBudget },
1721
- ...options.reasoningEffort === void 0 ? {} : { reasoning_effort: String(options.reasoningEffort) }
1722
- },
1723
- chat_context: {
1724
- chatPrompt: "",
1725
- imageUrls: null,
1726
- extra: {
1727
- context: [],
1728
- modelConfig: {
1729
- key: modelKey,
1730
- is_reasoning: isReasoning
1731
- },
1732
- originalContent: lastUserText
1733
- },
1734
- features: [],
1735
- text: lastUserText
1736
- },
1737
- model_config: {
1738
- key: modelKey,
1739
- is_reasoning: isReasoning,
1740
- max_output_tokens: maxTokens,
1741
- source: model?.source || "system",
1742
- ...contextConfig === void 0 ? {} : { context_config: contextConfig }
1743
- },
1801
+ tools: translateTools(options.tools),
1802
+ maxTokens,
1803
+ ...contextLength === void 0 ? {} : { contextLength },
1804
+ ...options.reasoningEffort === void 0 ? {} : { reasoningEffort: String(options.reasoningEffort) },
1805
+ isReasoning,
1806
+ ...contextConfig === void 0 ? {} : { contextConfig },
1807
+ lastUserText,
1744
1808
  business: {
1745
1809
  product: "cli",
1746
1810
  version: qoderIdeVersion,
@@ -1952,7 +2016,7 @@ var QoderImageUploader = class {
1952
2016
  reason: first.reason
1953
2017
  });
1954
2018
  try {
1955
- refreshed = await this.refreshCredentials(signal);
2019
+ refreshed = await this.refreshCredentials(credentials, signal);
1956
2020
  } catch (error) {
1957
2021
  if (signal.aborted) throw aborted$2();
1958
2022
  this.warnDegraded("credential refresh failed", error);
@@ -2263,15 +2327,18 @@ var QoderUsageReader = class {
2263
2327
  }
2264
2328
  async readAccount(pat, options) {
2265
2329
  if (!pat || typeof pat !== "string") throw new QoderLlmError("Qoder Personal Access Token is missing or invalid.", "MISSING_CREDENTIAL");
2266
- const cacheKey = `${this.region}:${opaqueCredentialKey(pat)}`;
2330
+ return this.readWithCredentials(opaqueCredentialKey(pat), (signal) => this.authService.getCredentials(pat, signal), options);
2331
+ }
2332
+ async readWithCredentials(key, resolve, options) {
2333
+ const cacheKey = `${this.region}:${key}`;
2267
2334
  if (!options?.force) {
2268
2335
  const cached = this.cache.get(cacheKey);
2269
2336
  if (cached && cached.expiresAt > Date.now()) return cached.info;
2270
2337
  }
2271
- return this.flights.run(cacheKey, options?.signal, (sharedSignal) => this.loadAccount(pat, sharedSignal, cacheKey), () => new QoderLlmError("Qoder account request was aborted.", "ABORTED"));
2338
+ return this.flights.run(cacheKey, options?.signal, (sharedSignal) => this.loadAccount(resolve, sharedSignal, cacheKey), () => new QoderLlmError("Qoder account request was aborted.", "ABORTED"));
2272
2339
  }
2273
- async loadAccount(pat, signal, cacheKey) {
2274
- const creds = await this.authService.getCredentials(pat, signal);
2340
+ async loadAccount(resolve, signal, cacheKey) {
2341
+ const creds = await resolve(signal);
2275
2342
  const profile = {
2276
2343
  id: creds.userID,
2277
2344
  name: creds.name || "Qoder User",
@@ -2457,7 +2524,7 @@ var QoderSearchClient = class {
2457
2524
  reason: first.reason
2458
2525
  });
2459
2526
  try {
2460
- credentials = await this.refreshCredentials(signal);
2527
+ credentials = await this.refreshCredentials(credentials, signal);
2461
2528
  } catch (error) {
2462
2529
  if (signal?.aborted) throw searchAborted(signal, error);
2463
2530
  throw new WebError(`Qoder credential refresh failed: ${String(error)}`, "WEB_PROVIDER_ERROR", { cause: error });
@@ -2580,188 +2647,838 @@ var QoderSearchClient = class {
2580
2647
  }
2581
2648
  };
2582
2649
  //#endregion
2583
- //#region src/qoder/transport/wire/sse.ts
2584
- /** Decode Qoder's outer SSE envelope and inner model chunks. */
2585
- const doneMarker = "[DONE]";
2586
- const defaultMaxSseBufferChars = 2 * 1024 * 1024;
2587
- function malformed(message) {
2588
- return new QoderLlmError(message, "MALFORMED_RESPONSE");
2650
+ //#region src/qoder/transport/request-compression.ts
2651
+ /** qodercli-compatible request compression and endpoint capability recovery. */
2652
+ const disabledEndpoints = /* @__PURE__ */ new Set();
2653
+ function endpointKey(protocol, url) {
2654
+ const { origin, pathname } = new URL(url);
2655
+ return `${protocol}:${origin}${pathname}`;
2589
2656
  }
2590
- function parseEnvelope(rawData, options) {
2591
- let value;
2592
- try {
2593
- value = JSON.parse(rawData);
2594
- } catch {
2595
- throw malformed("Malformed outer SSE JSON payload received from Qoder.");
2657
+ function vpcEndpoint(url) {
2658
+ const host = new URL(url).hostname.toLowerCase().replace(/\.$/u, "");
2659
+ return host === "vpc.qoder.com.cn" || host.endsWith(".vpc.qoder.com.cn");
2660
+ }
2661
+ function envInteger(value, fallback, max = Number.MAX_SAFE_INTEGER) {
2662
+ if (value === void 0 || !value.trim()) return fallback;
2663
+ const parsed = Number(value);
2664
+ return Number.isSafeInteger(parsed) && parsed >= 1 && parsed <= max ? parsed : fallback;
2665
+ }
2666
+ function compressionRejection(status, text, protocol) {
2667
+ if (status === 415 && /zstd|content[- ]encoding|compressed (?:request|body)/iu.test(text)) return "unsupported_encoding";
2668
+ if (status === 400 && /invalid.*json|invalid character|parse.*json|unexpected token|not valid json/iu.test(text) && /\\x28|\\u0028|0x28|(?:character|token)\s*['"]\(['"]/iu.test(text)) return "invalid_json_at_origin";
2669
+ if (protocol === "legacy" && status === 403) {
2670
+ let payload;
2671
+ try {
2672
+ payload = JSON.parse(text);
2673
+ } catch {
2674
+ return;
2675
+ }
2676
+ if (payload && typeof payload === "object") {
2677
+ const root = payload;
2678
+ const error = root.error && typeof root.error === "object" ? root.error : root;
2679
+ if (String(error.code) === "101" && /signature (?:is )?invalid/iu.test(text)) return "signature_invalid";
2680
+ }
2596
2681
  }
2597
- if (value === null || typeof value !== "object") throw malformed("Malformed outer SSE envelope received from Qoder.");
2598
- const envelope = value;
2599
- if (envelope.statusCodeValue !== void 0 && typeof envelope.statusCodeValue !== "number") throw malformed("Qoder SSE envelope has an invalid status code.");
2600
- 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.");
2601
- if (envelope.statusCodeValue !== void 0 && envelope.statusCodeValue !== 200) throw qoderModelError(`Qoder service returned upstream error status ${envelope.statusCodeValue}`, {
2602
- status: envelope.statusCodeValue,
2603
- cause: envelope.body ?? envelope.message,
2604
- source: "sse",
2605
- headers: options.headers,
2606
- httpStatus: options.httpStatus
2607
- });
2608
- if (envelope.body !== void 0 && typeof envelope.body !== "string") throw malformed("Qoder SSE envelope has an invalid model body.");
2609
- return envelope;
2610
2682
  }
2611
- function parseInner(body) {
2683
+ /** Probe only rejection responses, without consuming the transport's response. */
2684
+ async function probe(response, protocol, signal) {
2685
+ if (![
2686
+ 400,
2687
+ 403,
2688
+ 415
2689
+ ].includes(response.status)) return void 0;
2690
+ const reader = response.clone().body?.getReader();
2691
+ if (!reader) return void 0;
2692
+ const deadline = AbortSignal.any([signal, AbortSignal.timeout(1e3)]);
2693
+ const decoder = new TextDecoder();
2694
+ let remaining = 8192;
2695
+ let text = "";
2696
+ let truncated = false;
2697
+ const interrupted = () => {
2698
+ reader.cancel().catch(() => void 0);
2699
+ };
2700
+ deadline.addEventListener("abort", interrupted, { once: true });
2612
2701
  try {
2613
- const value = JSON.parse(body);
2614
- if (value === null || typeof value !== "object") throw new Error("not an object");
2615
- return value;
2702
+ if (deadline.aborted) return void 0;
2703
+ while (remaining > 0 && !deadline.aborted) {
2704
+ const { value, done } = await reader.read();
2705
+ if (done) break;
2706
+ if (value.byteLength >= remaining) truncated = true;
2707
+ text += decoder.decode(value.subarray(0, remaining), { stream: true });
2708
+ remaining -= Math.min(value.byteLength, remaining);
2709
+ try {
2710
+ JSON.parse(text);
2711
+ break;
2712
+ } catch {}
2713
+ }
2714
+ text += decoder.decode();
2715
+ return {
2716
+ status: response.status,
2717
+ contentType: response.headers.get("content-type"),
2718
+ serverRequestId: response.headers.get("x-request-id") ?? void 0,
2719
+ body: text,
2720
+ bodyTruncated: truncated,
2721
+ probeTimedOut: deadline.aborted,
2722
+ reason: deadline.aborted ? void 0 : compressionRejection(response.status, text, protocol)
2723
+ };
2616
2724
  } catch {
2617
- throw malformed("Malformed inner model payload received from Qoder.");
2725
+ return;
2726
+ } finally {
2727
+ deadline.removeEventListener("abort", interrupted);
2728
+ reader.cancel().catch(() => void 0);
2618
2729
  }
2619
2730
  }
2620
- function tokenUsage(innerChunk) {
2621
- if (!innerChunk.usage) return void 0;
2622
- const usage = innerChunk.usage;
2623
- const promptTokens = usage.prompt_tokens ?? 0;
2624
- const cacheReadTokens = usage.prompt_tokens_details?.cached_tokens ?? 0;
2625
- const cacheWriteTokens = usage.prompt_tokens_details?.cache_write_tokens ?? 0;
2626
- const reasoningTokens = usage.completion_tokens_details?.reasoning_tokens;
2627
- return {
2628
- inputTokens: Math.max(0, promptTokens - cacheReadTokens - cacheWriteTokens),
2629
- outputTokens: usage.completion_tokens ?? 0,
2630
- ...cacheReadTokens > 0 ? { cacheReadTokens } : {},
2631
- ...cacheWriteTokens > 0 ? { cacheWriteTokens } : {},
2632
- ...reasoningTokens === void 0 ? {} : { reasoningTokens }
2633
- };
2634
- }
2635
- function finishKind(value) {
2636
- if (value === void 0 || value === null || value === "") return void 0;
2637
- if (value === "stop") return "stop";
2638
- if (value === "length") return "max-tokens";
2639
- if (value === "tool_calls" || value === "toolUse" || value === "function_call") return "tool-calls";
2640
- if (value === "content_filter") throw new QoderLlmError("Qoder blocked the response through its content filter.", "PROVIDER_ERROR");
2641
- throw malformed(`Qoder returned unknown finish reason "${value}".`);
2642
- }
2643
- /** Whether a choice delta streams content, reasoning, or tool-call data. Control-only frames do not. */
2644
- function carriesStreamDelta(delta) {
2645
- if (delta == null) return false;
2646
- if (Array.isArray(delta.tool_calls) && delta.tool_calls.length > 0) return true;
2647
- if (typeof delta.content === "string" && delta.content) return true;
2648
- if (typeof delta.reasoning_content === "string" && delta.reasoning_content) return true;
2649
- return false;
2650
- }
2651
- async function* parseQoderSse(stream, options = {}) {
2652
- const reader = stream.getReader();
2653
- const decoder = new TextDecoder();
2654
- const toolCalls = /* @__PURE__ */ new Map();
2655
- let buffer = "";
2656
- let sourceEnded = false;
2657
- let sawDone = false;
2658
- let nextBlockIndex = 0;
2659
- let activeTextual;
2660
- let pendingUsage;
2661
- let terminalKind = "stop";
2662
- let sawFinishReason = false;
2663
- let sawContent = false;
2664
- const maxBufferChars = options.maxBufferChars ?? defaultMaxSseBufferChars;
2665
- const closeTextual = () => {
2666
- if (activeTextual === void 0) return [];
2667
- const state = activeTextual;
2668
- activeTextual = void 0;
2669
- return [{
2670
- type: "block-end",
2671
- index: state.index,
2672
- block: state.type === "text" ? {
2673
- type: "text",
2674
- text: state.text
2675
- } : {
2676
- type: "reasoning",
2677
- text: state.text
2678
- }
2679
- }];
2680
- };
2681
- const appendSegment = (segment) => {
2682
- if (!segment.text) return [];
2683
- const chunks = [];
2684
- sawContent = true;
2685
- if (activeTextual?.type !== segment.type) {
2686
- chunks.push(...closeTextual());
2687
- activeTextual = {
2688
- type: segment.type,
2689
- index: nextBlockIndex++,
2690
- text: ""
2731
+ /** One instance per prepared model request; endpoint decisions survive requests. */
2732
+ var QoderRequestCompression = class {
2733
+ fallbackAttempted = false;
2734
+ pendingConfirmation;
2735
+ compressedEndpoint;
2736
+ diagnostics;
2737
+ async send(options) {
2738
+ const { url, protocol, body, headers, signal, logger, requestId } = options;
2739
+ let identityReason;
2740
+ const dispatch = (wireBody, wireHeaders) => {
2741
+ this.diagnostics = {
2742
+ encoding: new Headers(wireHeaders).get("content-encoding") ?? "identity",
2743
+ originalBytes: Buffer.byteLength(body),
2744
+ sentBytes: typeof wireBody === "string" ? Buffer.byteLength(wireBody) : wireBody.byteLength,
2745
+ ...identityReason === void 0 ? {} : { reason: identityReason }
2691
2746
  };
2692
- chunks.push({
2693
- type: "block-start",
2694
- index: activeTextual.index,
2695
- blockType: segment.type
2747
+ logger?.debug?.("[Qoder Request] Dispatch", {
2748
+ requestId,
2749
+ protocol,
2750
+ ...this.diagnostics
2696
2751
  });
2752
+ return options.dispatch(wireBody, wireHeaders);
2753
+ };
2754
+ signal.throwIfAborted();
2755
+ const enabled = options.enabled ?? false;
2756
+ const key = endpointKey(protocol, url);
2757
+ const minChars = envInteger(process.env.QODER_ZSTD_REQUEST_BODY_MIN_CHARS, 4096);
2758
+ const level = envInteger(process.env.QODER_ZSTD_REQUEST_BODY_LEVEL, 1, 22);
2759
+ const existingEncoding = new Headers(headers).get("content-encoding");
2760
+ identityReason = !enabled ? "disabled" : this.fallbackAttempted ? "fallback_already_attempted" : vpcEndpoint(url) ? "vpc_endpoint" : disabledEndpoints.has(key) ? "endpoint_disabled" : body.length < minChars ? "below_threshold" : typeof zlib.zstdCompress !== "function" || zlib.constants.ZSTD_c_compressionLevel === void 0 ? "native_unavailable" : existingEncoding && existingEncoding !== "identity" ? "existing_encoding" : void 0;
2761
+ if (identityReason) return dispatch(body, headers);
2762
+ let compressed;
2763
+ const original = Buffer.from(body, "utf8");
2764
+ try {
2765
+ compressed = await promisify(zlib.zstdCompress)(original, { params: { [zlib.constants.ZSTD_c_compressionLevel]: level } });
2766
+ } catch {
2767
+ signal.throwIfAborted();
2768
+ identityReason = "compression_error";
2769
+ logger?.warn?.("[Qoder Request] Zstd compression failed; sending identity", {
2770
+ requestId,
2771
+ protocol
2772
+ });
2773
+ return dispatch(body, headers);
2697
2774
  }
2698
- activeTextual.text += segment.text;
2699
- chunks.push(segment.type === "text" ? {
2700
- type: "text-delta",
2701
- index: activeTextual.index,
2702
- text: segment.text
2703
- } : {
2704
- type: "reasoning-delta",
2705
- index: activeTextual.index,
2706
- text: segment.text
2775
+ signal.throwIfAborted();
2776
+ if (compressed.byteLength >= original.byteLength) {
2777
+ identityReason = "not_smaller";
2778
+ return dispatch(body, headers);
2779
+ }
2780
+ const identityHeaders = Object.fromEntries(Object.entries(headers).filter(([key]) => !["content-length", "content-encoding"].includes(key.toLowerCase())));
2781
+ logger?.debug?.("[Qoder Request] Sending zstd", {
2782
+ requestId,
2783
+ protocol,
2784
+ originalBytes: original.byteLength,
2785
+ compressedBytes: compressed.byteLength,
2786
+ level
2707
2787
  });
2708
- return chunks;
2709
- };
2710
- const openToolCall = (state) => {
2711
- if (!state.id || state.blockIndex !== void 0) return [];
2712
- const chunks = closeTextual();
2713
- state.blockIndex = nextBlockIndex++;
2714
- sawContent = true;
2715
- chunks.push({
2716
- type: "block-start",
2717
- index: state.blockIndex,
2718
- blockType: "tool-call"
2788
+ this.compressedEndpoint = key;
2789
+ const response = await dispatch(new Uint8Array(compressed), {
2790
+ ...identityHeaders,
2791
+ "Content-Encoding": "zstd"
2719
2792
  });
2720
- const argumentsDelta = state.arguments.slice(state.emittedArguments);
2721
- state.emittedArguments = state.arguments.length;
2722
- chunks.push({
2723
- type: "tool-call-delta",
2724
- index: state.blockIndex,
2725
- id: ToolCallId(state.id),
2726
- ...state.name ? { name: state.name } : {},
2727
- argumentsDelta
2793
+ const rejection = await probe(response, protocol, signal);
2794
+ signal.throwIfAborted();
2795
+ if (rejection && options.onRejection) try {
2796
+ await options.onRejection(rejection);
2797
+ } catch {
2798
+ logger?.warn?.("[Qoder Request] Compression rejection capture failed", {
2799
+ requestId,
2800
+ protocol,
2801
+ status: response.status
2802
+ });
2803
+ }
2804
+ signal.throwIfAborted();
2805
+ if (!rejection?.reason) return response;
2806
+ if (protocol === "legacy") {
2807
+ response.body?.cancel().catch(() => void 0);
2808
+ this.rejectLegacy(response.status, rejection.reason);
2809
+ }
2810
+ this.fallbackAttempted = true;
2811
+ identityReason = rejection.reason;
2812
+ logger?.debug?.("[Qoder Request] Retrying identity", {
2813
+ requestId,
2814
+ protocol,
2815
+ reason: rejection.reason,
2816
+ status: response.status
2728
2817
  });
2729
- return chunks;
2730
- };
2731
- try {
2732
- while (!sourceEnded && !sawDone) {
2733
- const { done, value } = await reader.read();
2734
- if (done) {
2735
- sourceEnded = true;
2736
- buffer += decoder.decode();
2737
- if (buffer.length > 0 && !buffer.endsWith("\n")) buffer += "\n";
2738
- } else {
2739
- options.onActivity?.();
2740
- buffer += decoder.decode(value, { stream: true });
2741
- if (buffer.length > maxBufferChars) throw malformed("Qoder SSE frame exceeded its size limit.");
2742
- }
2743
- while (!sawDone) {
2744
- const lineEnd = buffer.indexOf("\n");
2818
+ response.body?.cancel().catch(() => void 0);
2819
+ const identityResponse = await dispatch(body, identityHeaders);
2820
+ this.pendingConfirmation = key;
2821
+ return identityResponse;
2822
+ }
2823
+ confirm() {
2824
+ if (this.pendingConfirmation) disabledEndpoints.add(this.pendingConfirmation);
2825
+ this.pendingConfirmation = void 0;
2826
+ this.compressedEndpoint = void 0;
2827
+ }
2828
+ /** Legacy may reject the encoding in its SSE envelope before model output. */
2829
+ rejectLegacyError(error) {
2830
+ if (!(error instanceof QoderLlmError) || !this.compressedEndpoint) return;
2831
+ const status = error.failure.status;
2832
+ const cause = error.cause;
2833
+ if (typeof status !== "number") return;
2834
+ const rejection = compressionRejection(status, typeof cause === "string" ? cause : JSON.stringify(cause) ?? "", "legacy");
2835
+ if (rejection) this.rejectLegacy(status, rejection);
2836
+ }
2837
+ rejectLegacy(status, reason) {
2838
+ if (this.compressedEndpoint) disabledEndpoints.add(this.compressedEndpoint);
2839
+ throw new QoderLlmError(`Qoder rejected Zstd (${reason}); retry without compression.`, "TRANSPORT", { status });
2840
+ }
2841
+ };
2842
+ //#endregion
2843
+ //#region src/qoder/transport/wire/queue.ts
2844
+ /** Decode queue fields from the provider's nested JSON envelopes, never from prose. */
2845
+ function records(value) {
2846
+ const pending = [value];
2847
+ const seen = /* @__PURE__ */ new Set();
2848
+ const result = [];
2849
+ for (let count = 0; pending.length && count < 64; count++) {
2850
+ let next = pending.shift();
2851
+ if (typeof next === "string") {
2852
+ if (next.length > 65536) continue;
2853
+ try {
2854
+ next = JSON.parse(next);
2855
+ } catch {
2856
+ continue;
2857
+ }
2858
+ }
2859
+ if (!next || typeof next !== "object" || Array.isArray(next) || seen.has(next)) continue;
2860
+ seen.add(next);
2861
+ const record = next;
2862
+ result.push(record);
2863
+ for (const key of [
2864
+ "error",
2865
+ "data",
2866
+ "result",
2867
+ "message",
2868
+ "body",
2869
+ "cause",
2870
+ "queue",
2871
+ "details",
2872
+ "qoderApiError",
2873
+ "providerError"
2874
+ ]) if (record[key] !== void 0) pending.push(record[key]);
2875
+ }
2876
+ return result;
2877
+ }
2878
+ function boolean(value) {
2879
+ return value === true || value === "true" ? true : value === false || value === "false" ? false : void 0;
2880
+ }
2881
+ function number(value) {
2882
+ if (typeof value !== "number" && (typeof value !== "string" || !value.trim())) return void 0;
2883
+ const parsed = Number(value);
2884
+ return Number.isFinite(parsed) && parsed >= 0 && parsed <= Number.MAX_SAFE_INTEGER / 1e3 ? parsed : void 0;
2885
+ }
2886
+ function status(record) {
2887
+ const result = {};
2888
+ for (const key of ["isQueued", "serviceAvailable"]) {
2889
+ const value = boolean(record[key]);
2890
+ if (value !== void 0) result[key] = value;
2891
+ }
2892
+ for (const key of [
2893
+ "queueCount",
2894
+ "waitTime",
2895
+ "retryAfterSeconds"
2896
+ ]) {
2897
+ const value = number(record[key]);
2898
+ if (value !== void 0) result[key] = value;
2899
+ }
2900
+ for (const key of ["modelKey", "queueType"]) {
2901
+ const value = record[key];
2902
+ if (typeof value === "string" && value.trim() && value.length <= 256) result[key] = value.trim();
2903
+ }
2904
+ return result;
2905
+ }
2906
+ function parseQueueStatus(value) {
2907
+ for (const record of records(value)) {
2908
+ const parsed = status(record);
2909
+ if (parsed.isQueued !== void 0 || parsed.serviceAvailable !== void 0) return parsed;
2910
+ }
2911
+ }
2912
+ function queuedError(value) {
2913
+ const candidates = records(value);
2914
+ if (!candidates.some((record) => String(record.code) === "10605" || boolean(record.isQueued) === true)) return void 0;
2915
+ return new QoderModelQueuedError(candidates.map(status).find((candidate) => candidate.isQueued === true || candidate.waitTime !== void 0) ?? candidates.map(status).find((candidate) => Object.keys(candidate).length > 0) ?? {});
2916
+ }
2917
+ //#endregion
2918
+ //#region src/qoder/transport/queue.ts
2919
+ /** Queue polling and lease cleanup, scoped to one logical model call. */
2920
+ function queueFailure(message, code = "QODER_QUEUE_FAILED") {
2921
+ const error = new QoderLlmError(message, code);
2922
+ error.upstreamCode = "10605";
2923
+ return error;
2924
+ }
2925
+ function checkAbort(signal) {
2926
+ if (signal?.aborted) throw new QoderLlmError("Qoder request was aborted.", "ABORTED");
2927
+ }
2928
+ const queueClock = {
2929
+ now: () => performance.now(),
2930
+ sleep: (ms, signal) => new Promise((resolve, reject) => {
2931
+ if (signal?.aborted) {
2932
+ reject(new QoderLlmError("Qoder request was aborted.", "ABORTED"));
2933
+ return;
2934
+ }
2935
+ const abort = () => {
2936
+ clearTimeout(timer);
2937
+ reject(new QoderLlmError("Qoder request was aborted.", "ABORTED"));
2938
+ };
2939
+ const timer = setTimeout(() => {
2940
+ signal?.removeEventListener("abort", abort);
2941
+ resolve();
2942
+ }, ms);
2943
+ signal?.addEventListener("abort", abort, { once: true });
2944
+ })
2945
+ };
2946
+ /** A cancellable deadline with no surviving timers, including failed operations. */
2947
+ async function bounded(ms, signal, run) {
2948
+ checkAbort(signal);
2949
+ const controller = new AbortController();
2950
+ const onAbort = () => controller.abort(signal?.reason);
2951
+ signal?.addEventListener("abort", onAbort, { once: true });
2952
+ const timer = setTimeout(() => controller.abort(/* @__PURE__ */ new Error("Qoder queue operation timed out.")), Math.max(1, Math.ceil(ms)));
2953
+ let stop = () => {};
2954
+ const interrupted = new Promise((_resolve, reject) => {
2955
+ stop = () => reject(signal?.aborted ? new QoderLlmError("Qoder request was aborted.", "ABORTED") : queueFailure("Qoder queue operation timed out."));
2956
+ controller.signal.addEventListener("abort", stop, { once: true });
2957
+ });
2958
+ try {
2959
+ return await Promise.race([run(controller.signal), interrupted]);
2960
+ } finally {
2961
+ clearTimeout(timer);
2962
+ signal?.removeEventListener("abort", onAbort);
2963
+ controller.signal.removeEventListener("abort", stop);
2964
+ controller.abort();
2965
+ }
2966
+ }
2967
+ async function readBody(response, signal) {
2968
+ if (!response.body) return "";
2969
+ const reader = response.body.getReader();
2970
+ const cancel = () => {
2971
+ reader.cancel().catch(() => {});
2972
+ };
2973
+ signal.addEventListener("abort", cancel, { once: true });
2974
+ const decoder = new TextDecoder();
2975
+ let bytes = 0;
2976
+ let text = "";
2977
+ try {
2978
+ checkAbort(signal);
2979
+ while (true) {
2980
+ const { done, value } = await reader.read();
2981
+ checkAbort(signal);
2982
+ if (done) return text + decoder.decode();
2983
+ bytes += value.byteLength;
2984
+ if (bytes > 65536) throw queueFailure("Qoder queue response exceeded its size limit.");
2985
+ text += decoder.decode(value, { stream: true });
2986
+ }
2987
+ } finally {
2988
+ signal.removeEventListener("abort", cancel);
2989
+ await reader.cancel().catch(() => {});
2990
+ reader.releaseLock();
2991
+ }
2992
+ }
2993
+ function queueInterval(seconds) {
2994
+ return Math.min(3e4, Math.max(500, seconds !== void 0 && Number.isFinite(seconds) ? seconds * 1e3 : 3e4));
2995
+ }
2996
+ var ModelQueue = class {
2997
+ callId = randomUUID();
2998
+ options;
2999
+ clock;
3000
+ maxWaitMs;
3001
+ models = /* @__PURE__ */ new Set();
3002
+ deferred = /* @__PURE__ */ new Set();
3003
+ waitMs = 0;
3004
+ recoveries = 0;
3005
+ constructor(options) {
3006
+ this.options = options;
3007
+ this.clock = options.clock ?? queueClock;
3008
+ this.maxWaitMs = options.maxWaitMs ?? 36e5;
3009
+ if (!Number.isSafeInteger(this.maxWaitMs) || this.maxWaitMs <= 0 || this.maxWaitMs > 2147483647) throw new Error("modelQueueMaxWaitMs must be a positive integer no greater than 2147483647");
3010
+ }
3011
+ publish(update) {
3012
+ try {
3013
+ this.options.onUpdate?.(update);
3014
+ } catch {
3015
+ this.options.logger?.warn?.("[Qoder Queue] Status observer failed");
3016
+ }
3017
+ }
3018
+ clear() {
3019
+ this.publish({
3020
+ type: "clear",
3021
+ callId: this.callId
3022
+ });
3023
+ }
3024
+ /** Register even a queue signal rejected by the output guard, so cleanup still runs. */
3025
+ entered(status) {
3026
+ const model = status.modelKey ?? this.options.model;
3027
+ if (this.models.has(model)) return;
3028
+ this.models.add(model);
3029
+ if (this.options.dshSessionId && this.options.onLease) {
3030
+ const key = JSON.stringify([
3031
+ this.options.region,
3032
+ this.options.subscriberId,
3033
+ this.options.requestSetId,
3034
+ model
3035
+ ]);
3036
+ if (this.options.onLease({
3037
+ key,
3038
+ sessionId: this.options.dshSessionId,
3039
+ release: () => this.release(model, Math.max(0, Date.now() - (this.options.runStartedAt ?? Date.now())))
3040
+ })) this.deferred.add(model);
3041
+ }
3042
+ }
3043
+ async wait(initial, requestId) {
3044
+ this.entered(initial);
3045
+ if (this.recoveries >= 10) throw queueFailure("Qoder queue recovery limit reached.", "QODER_QUEUE_LIMIT");
3046
+ const started = this.clock.now();
3047
+ const remaining = () => this.maxWaitMs - this.waitMs - (this.clock.now() - started);
3048
+ const assertTime = () => {
3049
+ checkAbort(this.options.signal);
3050
+ if (remaining() <= 0) throw queueFailure("Qoder model queue wait timed out.", "QODER_QUEUE_TIMEOUT");
3051
+ };
3052
+ let status = initial;
3053
+ let failures = 0;
3054
+ let first = true;
3055
+ try {
3056
+ while (true) {
3057
+ assertTime();
3058
+ if (this.options.dshSessionId !== void 0) this.publish({
3059
+ type: "waiting",
3060
+ snapshot: {
3061
+ callId: this.callId,
3062
+ sessionId: this.options.dshSessionId,
3063
+ model: status.modelKey ?? this.options.model,
3064
+ ...this.options.modelName ? { modelName: this.options.modelName } : {},
3065
+ ...status.queueCount === void 0 ? {} : { queueCount: status.queueCount },
3066
+ ...status.waitTime === void 0 ? {} : { waitTimeMs: status.waitTime * 1e3 },
3067
+ ...status.serviceAvailable === void 0 ? {} : { serviceAvailable: status.serviceAvailable },
3068
+ waitElapsedMs: this.waitMs + Math.max(0, this.clock.now() - started),
3069
+ maxWaitMs: this.maxWaitMs,
3070
+ updatedAt: Date.now()
3071
+ }
3072
+ });
3073
+ if (!first) await this.clock.sleep(Math.min(queueInterval(status.retryAfterSeconds), remaining()), this.options.signal);
3074
+ first = false;
3075
+ assertTime();
3076
+ try {
3077
+ const next = await this.poll(status, requestId, remaining());
3078
+ status = {
3079
+ ...next,
3080
+ modelKey: next.modelKey ?? status.modelKey,
3081
+ queueType: next.queueType ?? status.queueType
3082
+ };
3083
+ failures = 0;
3084
+ this.entered(status);
3085
+ if (status.isQueued === false) {
3086
+ if (status.retryAfterSeconds !== void 0 && status.retryAfterSeconds > 0) await this.clock.sleep(Math.min(queueInterval(status.retryAfterSeconds), remaining()), this.options.signal);
3087
+ assertTime();
3088
+ this.recoveries++;
3089
+ return;
3090
+ }
3091
+ if (status.isQueued !== true && status.serviceAvailable !== false) throw queueFailure("Qoder returned an invalid queue state.", "QODER_QUEUE_INVALID_STATE");
3092
+ } catch (error) {
3093
+ assertTime();
3094
+ if (error instanceof QoderLlmError && [
3095
+ "QODER_QUEUE_UNAVAILABLE",
3096
+ "QODER_QUEUE_INVALID_STATE",
3097
+ "QODER_QUEUE_IDENTITY"
3098
+ ].includes(error.code)) throw error;
3099
+ if (++failures >= 3) throw queueFailure("Qoder queue status polling failed three consecutive times.");
3100
+ this.options.logger?.warn?.("[Qoder Queue] Status poll failed; will retry", { failures });
3101
+ status = {
3102
+ ...status,
3103
+ retryAfterSeconds: void 0
3104
+ };
3105
+ }
3106
+ }
3107
+ } finally {
3108
+ this.waitMs += Math.max(0, this.clock.now() - started);
3109
+ this.clear();
3110
+ }
3111
+ }
3112
+ async poll(status, requestId, remainingMs) {
3113
+ const url = getQoderQueueStatusUrl(this.options.region, {
3114
+ requestSetId: this.options.requestSetId,
3115
+ modelKey: status.modelKey ?? this.options.model,
3116
+ queueType: status.queueType
3117
+ });
3118
+ return bounded(remainingMs, this.options.signal, async (signal) => {
3119
+ for (let attempt = 0; attempt < 2; attempt++) {
3120
+ const credentials = await this.options.credentials(attempt > 0, signal);
3121
+ checkAbort(signal);
3122
+ const outcome = await bounded(Math.min(3e4, remainingMs), signal, async (httpSignal) => {
3123
+ const response = await this.options.fetch(url, {
3124
+ method: "GET",
3125
+ headers: {
3126
+ ...attributionHeaders(),
3127
+ ...buildAuthHeaders(null, url, credentials),
3128
+ Accept: "application/json",
3129
+ "Accept-Encoding": "identity",
3130
+ "X-Request-Id": requestId,
3131
+ "X-Session-ID": this.options.sessionId
3132
+ },
3133
+ signal: httpSignal
3134
+ });
3135
+ return {
3136
+ status: response.status,
3137
+ ok: response.ok,
3138
+ body: await readBody(response, httpSignal)
3139
+ };
3140
+ });
3141
+ if ((outcome.status === 401 || outcome.status === 403) && attempt === 0) continue;
3142
+ if (outcome.status === 404) throw queueFailure("Qoder queue endpoint is unavailable.", "QODER_QUEUE_UNAVAILABLE");
3143
+ if (!outcome.ok) throw queueFailure(`Qoder queue status returned HTTP ${outcome.status}.`);
3144
+ const parsed = parseQueueStatus(outcome.body);
3145
+ if (!parsed) throw queueFailure("Qoder returned a malformed queue status.");
3146
+ return parsed;
3147
+ }
3148
+ throw queueFailure("Qoder queue authentication failed.");
3149
+ });
3150
+ }
3151
+ /** Best-effort cleanup has an independent signal even after caller cancellation. */
3152
+ async finish(elapsedMs) {
3153
+ this.clear();
3154
+ const models = [...this.models].filter((model) => !this.deferred.has(model));
3155
+ this.models.clear();
3156
+ await Promise.allSettled(models.map((model) => this.release(model, elapsedMs)));
3157
+ }
3158
+ async release(model, elapsedMs) {
3159
+ try {
3160
+ await bounded(3e3, void 0, async (signal) => {
3161
+ const credentials = await this.options.credentials(false, signal);
3162
+ checkAbort(signal);
3163
+ const url = getQoderQueueFinishUrl(this.options.region);
3164
+ const body = JSON.stringify({
3165
+ payload: JSON.stringify({
3166
+ model_key: model,
3167
+ request_set_id: this.options.requestSetId,
3168
+ user_id: credentials.userID,
3169
+ time_consumed: Math.max(0, Math.round(elapsedMs))
3170
+ }),
3171
+ encodeVersion: "1"
3172
+ });
3173
+ const response = await this.options.fetch(url, {
3174
+ method: "POST",
3175
+ headers: {
3176
+ ...attributionHeaders(),
3177
+ ...buildAuthHeaders(body, url, credentials),
3178
+ "Content-Type": "application/json",
3179
+ Accept: "application/json",
3180
+ "X-Request-Id": randomUUID()
3181
+ },
3182
+ body,
3183
+ signal
3184
+ });
3185
+ await readBody(response, signal);
3186
+ if (!response.ok) throw queueFailure("Qoder lease release failed.");
3187
+ });
3188
+ } catch {
3189
+ this.options.logger?.warn?.("[Qoder Queue] Finish failed; server TTL will release the lease");
3190
+ }
3191
+ }
3192
+ };
3193
+ //#endregion
3194
+ //#region src/qoder/transport/wire/serialize.ts
3195
+ async function buildQoderRequestBody(options, userId, translatedMessages, model, attachments, tracker, identityScope = "") {
3196
+ const prepared = await prepareQoderRequest(options, userId, translatedMessages, model, attachments, tracker, identityScope);
3197
+ return {
3198
+ request_id: prepared.requestId,
3199
+ request_set_id: prepared.requestSetId,
3200
+ chat_record_id: prepared.chatRecordId,
3201
+ session_id: prepared.sessionId,
3202
+ stream: true,
3203
+ chat_task: "FREE_INPUT",
3204
+ is_reply: true,
3205
+ is_retry: false,
3206
+ source: 1,
3207
+ version: "3",
3208
+ session_type: "qodercli",
3209
+ agent_id: "agent_common",
3210
+ task_id: prepared.taskId,
3211
+ code_language: "",
3212
+ chat_prompt: "",
3213
+ image_urls: null,
3214
+ aliyun_user_type: "",
3215
+ system: prepared.messages.filter((message) => message.role === "system").flatMap((message) => typeof message.content === "string" && message.content.length > 0 ? [{
3216
+ type: "text",
3217
+ text: message.content
3218
+ }] : []),
3219
+ messages: prepared.messages,
3220
+ tools: prepared.tools,
3221
+ parameters: {
3222
+ max_tokens: prepared.maxTokens,
3223
+ ...prepared.contextLength === void 0 ? {} : { context_length: prepared.contextLength },
3224
+ ...prepared.reasoningEffort === void 0 ? {} : { reasoning_effort: prepared.reasoningEffort }
3225
+ },
3226
+ chat_context: {
3227
+ chatPrompt: "",
3228
+ imageUrls: null,
3229
+ extra: {
3230
+ context: [],
3231
+ modelConfig: {
3232
+ key: prepared.modelKey,
3233
+ is_reasoning: prepared.isReasoning
3234
+ },
3235
+ originalContent: prepared.lastUserText
3236
+ },
3237
+ features: [],
3238
+ text: prepared.lastUserText
3239
+ },
3240
+ model_config: {
3241
+ key: prepared.modelKey,
3242
+ is_reasoning: prepared.isReasoning,
3243
+ max_output_tokens: prepared.maxTokens,
3244
+ source: prepared.modelSource,
3245
+ ...prepared.contextConfig === void 0 ? {} : { context_config: prepared.contextConfig }
3246
+ },
3247
+ business: prepared.business
3248
+ };
3249
+ }
3250
+ //#endregion
3251
+ //#region src/qoder/transport/wire/stream-chunks.ts
3252
+ /** Convert decoded Qoder model frames into DSH stream chunks. */
3253
+ const doneMarker = "[DONE]";
3254
+ const defaultMaxSseBufferChars = 2 * 1024 * 1024;
3255
+ function malformed$1(message) {
3256
+ return new QoderLlmError(message, "MALFORMED_RESPONSE");
3257
+ }
3258
+ /** Distinguish invalid JSON from a valid JSON value with an invalid model shape. */
3259
+ var QoderMalformedJsonFrameError = class extends QoderLlmError {
3260
+ incomplete;
3261
+ auxiliary;
3262
+ constructor(incomplete = false, auxiliary = false) {
3263
+ super("Malformed model-server SSE JSON payload.", "MALFORMED_RESPONSE");
3264
+ this.incomplete = incomplete;
3265
+ this.auxiliary = auxiliary;
3266
+ }
3267
+ };
3268
+ function tokenUsage(innerChunk) {
3269
+ if (!innerChunk.usage) return void 0;
3270
+ const usage = innerChunk.usage;
3271
+ const promptTokens = usage.prompt_tokens ?? 0;
3272
+ const cacheReadTokens = usage.prompt_tokens_details?.cached_tokens ?? 0;
3273
+ const cacheWriteTokens = usage.prompt_tokens_details?.cache_write_tokens ?? 0;
3274
+ const reasoningTokens = usage.completion_tokens_details?.reasoning_tokens;
3275
+ return {
3276
+ inputTokens: Math.max(0, promptTokens - cacheReadTokens - cacheWriteTokens),
3277
+ outputTokens: usage.completion_tokens ?? 0,
3278
+ ...cacheReadTokens > 0 ? { cacheReadTokens } : {},
3279
+ ...cacheWriteTokens > 0 ? { cacheWriteTokens } : {},
3280
+ ...reasoningTokens === void 0 ? {} : { reasoningTokens }
3281
+ };
3282
+ }
3283
+ function finishKind(value) {
3284
+ if (value === void 0 || value === null || value === "") return void 0;
3285
+ if (value === "stop") return "stop";
3286
+ if (value === "length") return "max-tokens";
3287
+ if (value === "tool_calls" || value === "toolUse" || value === "function_call") return "tool-calls";
3288
+ if (value === "content_filter") throw new QoderLlmError("Qoder blocked the response through its content filter.", "PROVIDER_ERROR");
3289
+ throw malformed$1(`Qoder returned unknown finish reason "${value}".`);
3290
+ }
3291
+ /** Whether a choice delta streams content, reasoning, or tool-call data. Control-only frames do not. */
3292
+ function carriesStreamDelta(delta) {
3293
+ if (delta == null) return false;
3294
+ if (Array.isArray(delta.tool_calls) && delta.tool_calls.length > 0) return true;
3295
+ if (typeof delta.content === "string" && delta.content) return true;
3296
+ if (typeof delta.reasoning_content === "string" && delta.reasoning_content) return true;
3297
+ return false;
3298
+ }
3299
+ async function* parseQoderChunks(stream, decodeFrame, options = {}, behavior = {}) {
3300
+ const reader = stream.getReader();
3301
+ const decoder = new TextDecoder();
3302
+ const toolCalls = /* @__PURE__ */ new Map();
3303
+ let buffer = "";
3304
+ let eventData = [];
3305
+ let continuedEventData = [];
3306
+ let eventDataChars = 0;
3307
+ let eventType;
3308
+ let sourceEnded = false;
3309
+ let sawDone = false;
3310
+ let nextBlockIndex = 0;
3311
+ let activeTextual;
3312
+ let pendingUsage;
3313
+ let terminalKind = "stop";
3314
+ let sawFinishReason = false;
3315
+ let sawContent = false;
3316
+ const maxBufferChars = options.maxBufferChars ?? defaultMaxSseBufferChars;
3317
+ const closeTextual = () => {
3318
+ if (activeTextual === void 0) return [];
3319
+ const state = activeTextual;
3320
+ activeTextual = void 0;
3321
+ return [{
3322
+ type: "block-end",
3323
+ index: state.index,
3324
+ block: state.type === "text" ? {
3325
+ type: "text",
3326
+ text: state.text
3327
+ } : {
3328
+ type: "reasoning",
3329
+ text: state.text
3330
+ }
3331
+ }];
3332
+ };
3333
+ const appendSegment = (segment) => {
3334
+ if (!segment.text) return [];
3335
+ const chunks = [];
3336
+ sawContent = true;
3337
+ if (activeTextual?.type !== segment.type) {
3338
+ chunks.push(...closeTextual());
3339
+ activeTextual = {
3340
+ type: segment.type,
3341
+ index: nextBlockIndex++,
3342
+ text: ""
3343
+ };
3344
+ chunks.push({
3345
+ type: "block-start",
3346
+ index: activeTextual.index,
3347
+ blockType: segment.type
3348
+ });
3349
+ }
3350
+ activeTextual.text += segment.text;
3351
+ chunks.push(segment.type === "text" ? {
3352
+ type: "text-delta",
3353
+ index: activeTextual.index,
3354
+ text: segment.text
3355
+ } : {
3356
+ type: "reasoning-delta",
3357
+ index: activeTextual.index,
3358
+ text: segment.text
3359
+ });
3360
+ return chunks;
3361
+ };
3362
+ const openToolCall = (state) => {
3363
+ if (!state.id || state.blockIndex !== void 0) return [];
3364
+ const chunks = closeTextual();
3365
+ state.blockIndex = nextBlockIndex++;
3366
+ sawContent = true;
3367
+ chunks.push({
3368
+ type: "block-start",
3369
+ index: state.blockIndex,
3370
+ blockType: "tool-call"
3371
+ });
3372
+ const argumentsDelta = state.arguments.slice(state.emittedArguments);
3373
+ state.emittedArguments = state.arguments.length;
3374
+ chunks.push({
3375
+ type: "tool-call-delta",
3376
+ index: state.blockIndex,
3377
+ id: ToolCallId(state.id),
3378
+ ...state.name ? { name: state.name } : {},
3379
+ argumentsDelta
3380
+ });
3381
+ return chunks;
3382
+ };
3383
+ try {
3384
+ while (!sourceEnded && !sawDone) {
3385
+ const { done, value } = await reader.read();
3386
+ if (done) {
3387
+ sourceEnded = true;
3388
+ buffer += decoder.decode();
3389
+ if (buffer.length > 0 && !buffer.endsWith("\n")) buffer += "\n";
3390
+ buffer += "\n";
3391
+ } else {
3392
+ options.onActivity?.();
3393
+ buffer += decoder.decode(value, { stream: true });
3394
+ if (buffer.length > maxBufferChars) throw malformed$1("Qoder SSE frame exceeded its size limit.");
3395
+ }
3396
+ while (!sawDone) {
3397
+ const lineEnd = buffer.indexOf("\n");
2745
3398
  if (lineEnd === -1) break;
2746
3399
  let line = buffer.substring(0, lineEnd);
2747
3400
  buffer = buffer.substring(lineEnd + 1);
2748
3401
  if (line.endsWith("\r")) line = line.slice(0, -1);
2749
- line = line.trim();
2750
- if (!line.startsWith("data:")) continue;
2751
- const rawData = line.substring(5).trim();
3402
+ if (line.startsWith("event:")) {
3403
+ eventType = line.substring(6).trim();
3404
+ continue;
3405
+ }
3406
+ if (line.startsWith("data:")) {
3407
+ const value = line.substring(5);
3408
+ const data = value.startsWith(" ") ? value.substring(1) : value;
3409
+ eventData.push(data);
3410
+ if (behavior.recoverUnprefixedContinuations) continuedEventData.push(data);
3411
+ eventDataChars += data.length + 1;
3412
+ if (eventDataChars > maxBufferChars) throw malformed$1("Qoder SSE frame exceeded its size limit.");
3413
+ continue;
3414
+ }
3415
+ if (line !== "") {
3416
+ const jsonPropertyContinuation = /^:\s*(?:-?\d|true\b|false\b|null\b|["{\[\\])/u.test(line);
3417
+ if (behavior.recoverUnprefixedContinuations && continuedEventData.length > 0 && (!line.startsWith(":") || jsonPropertyContinuation) && !/^[a-zA-Z][\w-]*(?::|$)/u.test(line)) {
3418
+ const index = continuedEventData.length - 1;
3419
+ continuedEventData[index] += line;
3420
+ eventDataChars += line.length;
3421
+ if (eventDataChars > maxBufferChars) throw malformed$1("Qoder SSE frame exceeded its size limit.");
3422
+ }
3423
+ continue;
3424
+ }
3425
+ const rawData = eventData.join("\n").trim();
3426
+ const continuedData = continuedEventData.join("\n").trim();
3427
+ const frameEventType = eventType;
3428
+ eventData = [];
3429
+ continuedEventData = [];
3430
+ eventDataChars = 0;
3431
+ eventType = void 0;
2752
3432
  if (!rawData) continue;
2753
3433
  if (rawData === doneMarker) {
3434
+ options.onControlFrame?.("done");
2754
3435
  sawDone = true;
2755
3436
  break;
2756
3437
  }
2757
- const envelope = parseEnvelope(rawData, options);
2758
- const body = typeof envelope.body === "string" ? envelope.body.trim() : "";
2759
- if (!body) continue;
2760
- if (body === doneMarker) {
3438
+ let innerChunk;
3439
+ let malformedDetails;
3440
+ try {
3441
+ const frameOptions = {
3442
+ ...options,
3443
+ onMalformedFrame: (details) => {
3444
+ malformedDetails = {
3445
+ ...details,
3446
+ atEof: sourceEnded,
3447
+ afterFinish: sawFinishReason
3448
+ };
3449
+ }
3450
+ };
3451
+ try {
3452
+ innerChunk = decodeFrame(rawData, frameOptions, frameEventType);
3453
+ } catch (error) {
3454
+ if (!(error instanceof QoderMalformedJsonFrameError) || !continuedData || continuedData === rawData) throw error;
3455
+ const originalDetails = malformedDetails;
3456
+ innerChunk = decodeFrame(continuedData, frameOptions, frameEventType);
3457
+ if (originalDetails) options.onMalformedFrame?.({
3458
+ ...originalDetails,
3459
+ disposition: "recovered-continuation"
3460
+ });
3461
+ }
3462
+ } catch (error) {
3463
+ const malformedJson = error instanceof QoderMalformedJsonFrameError ? error : void 0;
3464
+ const skippedAfterFinish = behavior.ignoreMalformedAfterFinish && sawFinishReason && frameEventType !== "error" && malformedJson !== void 0;
3465
+ const skippedAuxiliary = !sourceEnded && behavior.allowIncompleteAuxiliaryFrames && malformedJson?.incomplete && malformedJson.auxiliary && frameEventType !== "error";
3466
+ if (malformedDetails !== void 0) options.onMalformedFrame?.({
3467
+ ...malformedDetails,
3468
+ emptyChoicesPrefix: malformedJson?.auxiliary ?? false,
3469
+ disposition: skippedAfterFinish ? "skipped-after-finish" : skippedAuxiliary ? "skipped-auxiliary" : "rejected"
3470
+ });
3471
+ if (skippedAfterFinish) continue;
3472
+ if (sourceEnded && error instanceof QoderMalformedJsonFrameError && error.incomplete) throw new QoderLlmError("Qoder SSE connection ended with an incomplete JSON event before model completion.", "TRANSPORT");
3473
+ if (skippedAuxiliary) continue;
3474
+ throw error;
3475
+ }
3476
+ if (innerChunk === void 0) continue;
3477
+ if (innerChunk === doneMarker) {
3478
+ options.onControlFrame?.("done");
2761
3479
  sawDone = true;
2762
3480
  break;
2763
3481
  }
2764
- const innerChunk = parseInner(body);
2765
3482
  pendingUsage = tokenUsage(innerChunk) ?? pendingUsage;
2766
3483
  for (const choice of innerChunk.choices ?? []) {
2767
3484
  const kind = finishKind(choice.finish_reason);
@@ -2780,11 +3497,11 @@ async function* parseQoderSse(stream, options = {}) {
2780
3497
  text: delta.content
2781
3498
  })) yield chunk;
2782
3499
  if (delta.tool_calls !== void 0) {
2783
- if (!Array.isArray(delta.tool_calls)) throw malformed("Qoder tool-call delta is not an array.");
3500
+ if (!Array.isArray(delta.tool_calls)) throw malformed$1("Qoder tool-call delta is not an array.");
2784
3501
  for (const rawCall of delta.tool_calls) {
2785
- if (typeof rawCall !== "object" || rawCall === null || Array.isArray(rawCall)) throw malformed("Qoder tool-call delta is not an object.");
3502
+ if (typeof rawCall !== "object" || rawCall === null || Array.isArray(rawCall)) throw malformed$1("Qoder tool-call delta is not an object.");
2786
3503
  const upstreamIndex = rawCall.index ?? 0;
2787
- if (!Number.isInteger(upstreamIndex) || upstreamIndex < 0) throw malformed("Qoder tool-call delta has an invalid index.");
3504
+ if (!Number.isInteger(upstreamIndex) || upstreamIndex < 0) throw malformed$1("Qoder tool-call delta has an invalid index.");
2788
3505
  let state = toolCalls.get(upstreamIndex);
2789
3506
  if (state === void 0) {
2790
3507
  state = {
@@ -2795,17 +3512,17 @@ async function* parseQoderSse(stream, options = {}) {
2795
3512
  };
2796
3513
  toolCalls.set(upstreamIndex, state);
2797
3514
  }
2798
- if (rawCall.id !== void 0) if (rawCall.id === "" || rawCall.id === null) {} else if (typeof rawCall.id !== "string") throw malformed("Qoder tool call has an invalid id.");
3515
+ if (rawCall.id !== void 0) if (rawCall.id === "" || rawCall.id === null) {} else if (typeof rawCall.id !== "string") throw malformed$1("Qoder tool call has an invalid id.");
2799
3516
  else {
2800
- if (state.id && state.id !== rawCall.id) throw malformed("Qoder changed a streamed tool-call id.");
3517
+ if (state.id && state.id !== rawCall.id) throw malformed$1("Qoder changed a streamed tool-call id.");
2801
3518
  state.id = rawCall.id;
2802
3519
  }
2803
3520
  let hasNewName = false;
2804
3521
  if (rawCall.function?.name !== void 0) {
2805
3522
  const name = rawCall.function.name;
2806
- if (name === "" || name === null) {} else if (typeof name !== "string") throw malformed("Qoder tool call has an invalid name.");
3523
+ if (name === "" || name === null) {} else if (typeof name !== "string") throw malformed$1("Qoder tool call has an invalid name.");
2807
3524
  else {
2808
- if (state.name && state.name !== name) throw malformed("Qoder changed a streamed tool-call name.");
3525
+ if (state.name && state.name !== name) throw malformed$1("Qoder changed a streamed tool-call name.");
2809
3526
  if (state.name !== name) {
2810
3527
  state.name = name;
2811
3528
  hasNewName = true;
@@ -2813,7 +3530,7 @@ async function* parseQoderSse(stream, options = {}) {
2813
3530
  }
2814
3531
  }
2815
3532
  if (rawCall.function?.arguments !== void 0) {
2816
- if (typeof rawCall.function.arguments !== "string") throw malformed("Qoder tool-call arguments delta is not a string.");
3533
+ if (typeof rawCall.function.arguments !== "string") throw malformed$1("Qoder tool-call arguments delta is not a string.");
2817
3534
  state.arguments += rawCall.function.arguments;
2818
3535
  }
2819
3536
  const wasOpen = state.blockIndex !== void 0;
@@ -2847,15 +3564,15 @@ async function* parseQoderSse(stream, options = {}) {
2847
3564
  if (!sawDone && !sawFinishReason) throw new QoderLlmError("SSE stream ended prematurely without [DONE].", "TRANSPORT");
2848
3565
  for (const chunk of closeTextual()) yield chunk;
2849
3566
  for (const [, state] of [...toolCalls.entries()].sort(([left], [right]) => left - right)) {
2850
- if (!state.id || !state.name) throw malformed("Qoder completed an unidentifiable tool call.");
3567
+ if (!state.id || !state.name) throw malformed$1("Qoder completed an unidentifiable tool call.");
2851
3568
  const normalizedArguments = state.arguments.trim() ? state.arguments : "{}";
2852
3569
  try {
2853
3570
  JSON.parse(normalizedArguments);
2854
3571
  } catch {
2855
- throw malformed(`Qoder completed tool call "${state.name}" with malformed JSON arguments.`);
3572
+ throw malformed$1(`Qoder completed tool call "${state.name}" with malformed JSON arguments.`);
2856
3573
  }
2857
3574
  for (const chunk of openToolCall(state)) yield chunk;
2858
- if (state.blockIndex === void 0) throw malformed("Qoder failed to open a completed tool call.");
3575
+ if (state.blockIndex === void 0) throw malformed$1("Qoder failed to open a completed tool call.");
2859
3576
  if (state.emittedArguments === 0 && normalizedArguments === "{}") yield {
2860
3577
  type: "tool-call-delta",
2861
3578
  index: state.blockIndex,
@@ -2897,14 +3614,127 @@ async function* parseQoderSse(stream, options = {}) {
2897
3614
  };
2898
3615
  }
2899
3616
  //#endregion
3617
+ //#region src/qoder/transport/wire/control-frames.ts
3618
+ function qoderControlFrameKind(data) {
3619
+ if (data === "[DONE]") return "done";
3620
+ if (data === "[NOT_EXCEED_QUOTA]") return "quota-available";
3621
+ if (data.startsWith("[EXCEED_QUOTA]")) return "quota-exceeded";
3622
+ if (data.startsWith("[NOTIFICATIONS]")) return "notifications";
3623
+ }
3624
+ //#endregion
3625
+ //#region src/qoder/transport/wire/sse.ts
3626
+ function malformed(message) {
3627
+ return new QoderLlmError(message, "MALFORMED_RESPONSE");
3628
+ }
3629
+ function decodeLegacyFrame(rawData, options) {
3630
+ const outerControlKind = qoderControlFrameKind(rawData);
3631
+ if (outerControlKind !== void 0) {
3632
+ options.onControlFrame?.(outerControlKind);
3633
+ return outerControlKind === "done" ? "[DONE]" : void 0;
3634
+ }
3635
+ let value;
3636
+ try {
3637
+ value = JSON.parse(rawData);
3638
+ } catch {
3639
+ throw malformed("Malformed outer SSE JSON payload received from Qoder.");
3640
+ }
3641
+ if (value === null || typeof value !== "object") throw malformed("Malformed outer SSE envelope received from Qoder.");
3642
+ const envelope = value;
3643
+ if (envelope.statusCodeValue !== void 0 && (typeof envelope.statusCodeValue !== "number" || !Number.isInteger(envelope.statusCodeValue) || envelope.statusCodeValue < 100 || envelope.statusCodeValue > 599)) throw malformed("Qoder SSE envelope has an invalid status code.");
3644
+ if (envelope.statusCodeValue !== void 0 && envelope.statusCodeValue !== 200) {
3645
+ const queued = options.queueRecovery ? queuedError(envelope.body ?? envelope) : void 0;
3646
+ if (queued) throw queued;
3647
+ throw qoderModelError(`Qoder service returned upstream error status ${envelope.statusCodeValue}`, {
3648
+ status: envelope.statusCodeValue,
3649
+ cause: envelope.body ?? envelope.message,
3650
+ source: "sse",
3651
+ headers: options.headers,
3652
+ httpStatus: options.httpStatus
3653
+ });
3654
+ }
3655
+ if (envelope.body !== void 0 && typeof envelope.body !== "string") throw malformed("Qoder SSE envelope has an invalid model body.");
3656
+ const body = typeof envelope.body === "string" ? envelope.body.trim() : "";
3657
+ if (!body) return void 0;
3658
+ const innerControlKind = qoderControlFrameKind(body);
3659
+ if (innerControlKind !== void 0) {
3660
+ if (innerControlKind !== "done") options.onControlFrame?.(innerControlKind);
3661
+ return innerControlKind === "done" ? "[DONE]" : void 0;
3662
+ }
3663
+ try {
3664
+ const inner = JSON.parse(body);
3665
+ if (inner === null || typeof inner !== "object") throw new Error("not an object");
3666
+ return inner;
3667
+ } catch {
3668
+ throw malformed("Malformed inner model payload received from Qoder.");
3669
+ }
3670
+ }
3671
+ function parseQoderSse(stream, options = {}) {
3672
+ return parseQoderChunks(stream, decodeLegacyFrame, options);
3673
+ }
3674
+ //#endregion
2900
3675
  //#region src/qoder/transport/chat.ts
2901
- /** Qoder chat request encoding, timeout lifecycle, and SSE streaming. */
3676
+ /** Qoder chat request encoding, queue recovery, timeout lifecycle, and SSE streaming. */
2902
3677
  function aborted$1(message) {
2903
3678
  return new QoderLlmError(message, "ABORTED");
2904
3679
  }
2905
- async function* streamQoderChat(options, model, credentials, messages, dependencies) {
2906
- const request = await buildQoderRequestBody(options, credentials.userID, messages, model);
2907
- request.session_type = dependencies.region === "china" ? "qoderclicn" : "qodercli";
3680
+ async function* streamQoderChat(options, model, credentials, messages, dependencies) {
3681
+ const identityScope = options.purpose === void 0 && dependencies.hostRunId !== void 0 ? JSON.stringify([dependencies.region, dependencies.hostRunId]) : dependencies.region;
3682
+ const request = await buildQoderRequestBody(options, credentials.userID, messages, model, void 0, void 0, identityScope);
3683
+ request.session_type = dependencies.region === "china" ? "qoderclicn" : "qodercli";
3684
+ const startedAt = performance.now();
3685
+ let activeCredentials = credentials;
3686
+ const resolveCredentials = async (refresh, signal) => {
3687
+ const next = dependencies.resolveCredentials ? await dependencies.resolveCredentials(refresh, signal) : refresh && dependencies.refreshCredentials ? await dependencies.refreshCredentials(activeCredentials, signal) : activeCredentials;
3688
+ if (next.userID !== credentials.userID) throw queueFailure("Qoder subscriber changed during queue recovery.", "QODER_QUEUE_IDENTITY");
3689
+ activeCredentials = next;
3690
+ return next;
3691
+ };
3692
+ const queue = new ModelQueue({
3693
+ region: dependencies.region,
3694
+ fetch: dependencies.fetch,
3695
+ subscriberId: credentials.userID,
3696
+ requestSetId: request.request_set_id,
3697
+ sessionId: request.session_id,
3698
+ dshSessionId: options.purpose === void 0 && options.sessionId !== void 0 ? String(options.sessionId) : void 0,
3699
+ runStartedAt: request.business.begin_at,
3700
+ model: request.model_config.key,
3701
+ modelName: model?.name,
3702
+ signal: options.signal,
3703
+ maxWaitMs: dependencies.modelQueueMaxWaitMs,
3704
+ credentials: resolveCredentials,
3705
+ onUpdate: dependencies.onQueueUpdate,
3706
+ onLease: dependencies.onQueueLease,
3707
+ logger: dependencies.logger,
3708
+ clock: dependencies.queueClock
3709
+ });
3710
+ let sawOutput = false;
3711
+ const clear = () => queue.clear();
3712
+ options.signal?.addEventListener("abort", clear, { once: true });
3713
+ try {
3714
+ for (;;) {
3715
+ if (options.signal?.aborted) throw aborted$1("Request was aborted.");
3716
+ try {
3717
+ for await (const chunk of streamAttempt(options, model, activeCredentials, request, dependencies)) {
3718
+ if (chunk.type !== "usage" && chunk.type !== "finish") sawOutput = true;
3719
+ yield chunk;
3720
+ }
3721
+ return;
3722
+ } catch (error) {
3723
+ if (!(error instanceof QoderModelQueuedError)) throw error;
3724
+ queue.entered(error.queue);
3725
+ if (sawOutput) throw queueFailure("Qoder queued a request after output began; replay was stopped.", "QODER_QUEUE_AFTER_OUTPUT");
3726
+ await queue.wait(error.queue, request.request_id);
3727
+ await resolveCredentials(false, options.signal ?? new AbortController().signal);
3728
+ request.request_id = randomUUID();
3729
+ request.chat_record_id = request.request_id;
3730
+ }
3731
+ }
3732
+ } finally {
3733
+ options.signal?.removeEventListener("abort", clear);
3734
+ await queue.finish(performance.now() - startedAt);
3735
+ }
3736
+ }
3737
+ async function* streamAttempt(options, model, credentials, request, dependencies) {
2908
3738
  const encodedBytes = qoderEncodeBodyBytes(JSON.stringify(request));
2909
3739
  const chatUrl = getQoderChatUrl(dependencies.region);
2910
3740
  const requestController = new AbortController();
@@ -2917,10 +3747,7 @@ async function* streamQoderChat(options, model, credentials, messages, dependenc
2917
3747
  let lastActivityAt = startedAt;
2918
3748
  const onCallerAbort = () => requestController.abort(options.signal?.reason);
2919
3749
  options.signal?.addEventListener("abort", onCallerAbort, { once: true });
2920
- let headerTimer = setTimeout(() => {
2921
- headerTimedOut = true;
2922
- requestController.abort("response header timeout");
2923
- }, dependencies.responseHeaderTimeoutMs);
3750
+ let headerTimer;
2924
3751
  const resetIdleTimer = () => {
2925
3752
  lastActivityAt = performance.now();
2926
3753
  if (idleTimer !== void 0) clearTimeout(idleTimer);
@@ -2940,16 +3767,35 @@ async function* streamQoderChat(options, model, credentials, messages, dependenc
2940
3767
  contextTier: markedTiers.length === 1 ? markedTiers[0][0] : null,
2941
3768
  context_length: request.parameters.context_length ?? null
2942
3769
  }));
3770
+ const compression = new QoderRequestCompression();
2943
3771
  let response;
2944
3772
  let activeCredentials = credentials;
2945
3773
  for (let attempt = 0; attempt < 2; attempt++) {
2946
3774
  if (options.signal?.aborted) throw aborted$1("Request was aborted.");
2947
- if (attempt > 0) headerTimer = setTimeout(() => {
2948
- headerTimedOut = true;
2949
- requestController.abort("response header timeout");
2950
- }, dependencies.responseHeaderTimeoutMs);
2951
- response = await dependencies.fetch(chatUrl, {
2952
- method: "POST",
3775
+ response = await compression.send({
3776
+ url: chatUrl,
3777
+ protocol: "legacy",
3778
+ body: encodedBytes.toString("latin1"),
3779
+ enabled: dependencies.requestCompression,
3780
+ signal: requestController.signal,
3781
+ logger: dependencies.logger,
3782
+ requestId: request.request_id,
3783
+ dispatch: async (wireBody, headers) => {
3784
+ headerTimer = setTimeout(() => {
3785
+ headerTimedOut = true;
3786
+ requestController.abort("response header timeout");
3787
+ }, dependencies.responseHeaderTimeoutMs);
3788
+ try {
3789
+ return await dependencies.fetch(chatUrl, {
3790
+ method: "POST",
3791
+ headers,
3792
+ body: wireBody,
3793
+ signal: requestController.signal
3794
+ });
3795
+ } finally {
3796
+ clearTimeout(headerTimer);
3797
+ }
3798
+ },
2953
3799
  headers: {
2954
3800
  "content-type": "application/json",
2955
3801
  "accept": "text/event-stream",
@@ -2959,10 +3805,10 @@ async function* streamQoderChat(options, model, credentials, messages, dependenc
2959
3805
  "x-model-key": options.model || "cmodel",
2960
3806
  "x-model-source": model?.source || "system",
2961
3807
  ...attributionHeaders(),
2962
- ...buildAuthHeaders(encodedBytes, chatUrl, activeCredentials)
2963
- },
2964
- body: encodedBytes,
2965
- signal: requestController.signal
3808
+ ...buildAuthHeaders(encodedBytes, chatUrl, activeCredentials),
3809
+ "X-Request-Id": request.request_id,
3810
+ "X-Session-ID": request.session_id
3811
+ }
2966
3812
  });
2967
3813
  clearTimeout(headerTimer);
2968
3814
  reqId = qoderRequestId(response.headers);
@@ -2981,6 +3827,8 @@ async function* streamQoderChat(options, model, credentials, messages, dependenc
2981
3827
  } catch (error) {
2982
3828
  if (requestController.signal.aborted) throw error;
2983
3829
  }
3830
+ const queued = queuedError(bodyText);
3831
+ if (queued) throw queued;
2984
3832
  const failure = qoderModelError(`Qoder upstream service returned HTTP ${response.status}`, {
2985
3833
  status: response.status,
2986
3834
  headers: response.headers,
@@ -3001,22 +3849,36 @@ async function* streamQoderChat(options, model, credentials, messages, dependenc
3001
3849
  let tokenUsage;
3002
3850
  let finishReason;
3003
3851
  const streamStartedAt = performance.now();
3004
- for await (const chunk of parseQoderSse(response.body, {
3005
- onActivity: resetIdleTimer,
3006
- headers: response.headers,
3007
- httpStatus: response.status
3008
- })) {
3009
- chunkCount++;
3010
- if (firstChunkDurationMs === void 0) {
3011
- firstChunkDurationMs = Math.round(performance.now() - startedAt);
3012
- dependencies.logger?.debug?.("[Qoder Stream] First chunk received", {
3013
- durationMs: firstChunkDurationMs,
3014
- ...reqId === void 0 ? {} : { requestId: reqId }
3015
- });
3852
+ try {
3853
+ for await (const chunk of parseQoderSse(response.body, {
3854
+ queueRecovery: true,
3855
+ onActivity: resetIdleTimer,
3856
+ headers: response.headers,
3857
+ httpStatus: response.status,
3858
+ onControlFrame: (kind) => {
3859
+ if (kind !== "done") dependencies.logger?.warn?.("[Qoder Stream] Control frame skipped", {
3860
+ transport: "legacy",
3861
+ kind,
3862
+ ...reqId === void 0 ? {} : { requestId: reqId }
3863
+ });
3864
+ }
3865
+ })) {
3866
+ compression.confirm();
3867
+ chunkCount++;
3868
+ if (firstChunkDurationMs === void 0) {
3869
+ firstChunkDurationMs = Math.round(performance.now() - startedAt);
3870
+ dependencies.logger?.debug?.("[Qoder Stream] First chunk received", {
3871
+ durationMs: firstChunkDurationMs,
3872
+ ...reqId === void 0 ? {} : { requestId: reqId }
3873
+ });
3874
+ }
3875
+ if (chunk.type === "usage") tokenUsage = chunk.usage;
3876
+ if (chunk.type === "finish") finishReason = chunk.reason.kind;
3877
+ yield chunk;
3016
3878
  }
3017
- if (chunk.type === "usage") tokenUsage = chunk.usage;
3018
- if (chunk.type === "finish") finishReason = chunk.reason.kind;
3019
- yield chunk;
3879
+ } catch (error) {
3880
+ if (chunkCount === 0) compression.rejectLegacyError(error);
3881
+ throw error;
3020
3882
  }
3021
3883
  dependencies.logger?.debug?.("[Qoder Stream] Stream completed", {
3022
3884
  durationMs: Math.round(performance.now() - startedAt),
@@ -3049,6 +3911,7 @@ async function* streamQoderChat(options, model, credentials, messages, dependenc
3049
3911
  });
3050
3912
  throw failure;
3051
3913
  }
3914
+ if (error instanceof QoderModelQueuedError) throw error;
3052
3915
  if (error instanceof QoderLlmError) {
3053
3916
  dependencies.logger?.error?.("[Qoder Stream] Request failed", redactLogValue(error), {
3054
3917
  chunkCount,
@@ -3073,13 +3936,399 @@ async function* streamQoderChat(options, model, credentials, messages, dependenc
3073
3936
  }
3074
3937
  }
3075
3938
  //#endregion
3939
+ //#region src/qoder/transport/browser-authorization.ts
3940
+ /** qodercli 1.1.73 PKCE device flow. No website password or agent runtime is needed. */
3941
+ var QoderBrowserAuthorization = class {
3942
+ options;
3943
+ constructor(options) {
3944
+ this.options = options;
3945
+ }
3946
+ async begin(signal) {
3947
+ signal.throwIfAborted();
3948
+ const verifier = randomBytes(48).toString("base64url");
3949
+ const challenge = createHash("sha256").update(verifier).digest("base64url");
3950
+ const nonce = randomUUID();
3951
+ const machineID = (this.options.resolveMachineId ?? (() => getMachineId(qoderMachineIdPaths(this.options.region))))();
3952
+ const query = new URLSearchParams({
3953
+ challenge,
3954
+ challenge_method: "S256",
3955
+ nonce,
3956
+ machine_id: machineID,
3957
+ client_id: "e883ade2-e6e3-4d6d-adf7-f92ceff5fdcb"
3958
+ });
3959
+ const origin = this.options.region === "china" ? "https://qoder.cn" : "https://qoder.com";
3960
+ const expiresAt = Date.now() + (this.options.loginTimeoutMs ?? 3e5);
3961
+ const deadline = AbortSignal.timeout(Math.max(1, expiresAt - Date.now()));
3962
+ const combined = AbortSignal.any([signal, deadline]);
3963
+ return {
3964
+ url: `${origin}/device/selectAccounts?${query}`,
3965
+ expiresAt,
3966
+ complete: async () => {
3967
+ try {
3968
+ const poll = new URL(`${resolveQoderEndpoints(this.options.region).openApiUrl}/api/v1/deviceToken/poll`);
3969
+ poll.search = new URLSearchParams({
3970
+ nonce,
3971
+ verifier,
3972
+ challenge_method: "S256"
3973
+ }).toString();
3974
+ while (!combined.aborted) {
3975
+ let response;
3976
+ try {
3977
+ response = await (this.options.fetch ?? fetch)(poll, {
3978
+ headers: {
3979
+ accept: "application/json",
3980
+ "user-agent": defaultUserAgent
3981
+ },
3982
+ signal: AbortSignal.any([combined, AbortSignal.timeout(15e3)])
3983
+ });
3984
+ } catch {
3985
+ if (combined.aborted) break;
3986
+ await setTimeout$1(this.options.pollIntervalMs ?? 1e3, void 0, { signal: combined });
3987
+ continue;
3988
+ }
3989
+ if (response.status === 404 || response.status >= 500 || response.status === 429) {
3990
+ await response.body?.cancel();
3991
+ await setTimeout$1(this.options.pollIntervalMs ?? 1e3, void 0, { signal: combined });
3992
+ continue;
3993
+ }
3994
+ if (!response.ok) {
3995
+ await response.body?.cancel();
3996
+ throw new QoderLlmError("Qoder browser authorization was rejected.", "AUTH");
3997
+ }
3998
+ const raw = JSON.parse(await readLimitedText(response, defaultMaxJsonBytes, "Qoder browser authorization"));
3999
+ return await this.grant(raw, machineID, combined);
4000
+ }
4001
+ throw new QoderLlmError("Qoder browser login timed out.", "TIMEOUT");
4002
+ } catch (error) {
4003
+ if (signal.aborted) throw new QoderLlmError("Qoder browser login was cancelled.", "ABORTED");
4004
+ if (deadline.aborted) throw new QoderLlmError("Qoder browser login timed out.", "TIMEOUT");
4005
+ if (error instanceof QoderLlmError) throw error;
4006
+ throw new QoderLlmError("Qoder browser authorization returned an invalid response.", "MALFORMED_RESPONSE");
4007
+ }
4008
+ }
4009
+ };
4010
+ }
4011
+ async refresh(grant, signal, rejectedToken) {
4012
+ if (grant.region !== this.options.region) throw new QoderLlmError("Qoder authorization region mismatch.", "AUTH");
4013
+ if (!(rejectedToken !== void 0 && rejectedToken === grant.credentials.authToken) && grant.expiresAt > Date.now() + 3e5) return grant;
4014
+ if (grant.refreshExpiresAt !== void 0 && grant.refreshExpiresAt <= Date.now()) throw new QoderLlmError("Qoder browser authorization expired. Sign in again.", "AUTH");
4015
+ const raw = await openApiJsonRequest(this.options.fetch ?? fetch, {
4016
+ url: `${resolveQoderEndpoints(this.options.region).openApiUrl}/api/v1/deviceToken/refresh`,
4017
+ body: {
4018
+ refresh_token: grant.refreshToken,
4019
+ machine_id: grant.credentials.machineID
4020
+ },
4021
+ signal,
4022
+ operation: "BrowserRefresh"
4023
+ });
4024
+ const token = raw.device_token ?? raw.token;
4025
+ if (!token || typeof token !== "string" || !raw.refresh_token || typeof raw.refresh_token !== "string") throw new QoderLlmError("Qoder refresh returned incomplete credentials.", "MALFORMED_RESPONSE");
4026
+ return {
4027
+ ...grant,
4028
+ credentials: {
4029
+ ...grant.credentials,
4030
+ authToken: token
4031
+ },
4032
+ refreshToken: raw.refresh_token,
4033
+ expiresAt: deviceExpiry(raw.expires_at, raw.expires_in) ?? Date.now() + 864e5,
4034
+ refreshExpiresAt: refreshExpiry(raw) ?? grant.refreshExpiresAt
4035
+ };
4036
+ }
4037
+ async grant(raw, machineID, signal) {
4038
+ if (!raw.token || typeof raw.token !== "string" || !raw.refresh_token || typeof raw.refresh_token !== "string") throw new QoderLlmError("Qoder browser authorization returned incomplete credentials.", "MALFORMED_RESPONSE");
4039
+ const credentials = await new QoderAuthManager({
4040
+ region: this.options.region,
4041
+ fetch: this.options.fetch
4042
+ }).credentialsForToken(raw.token, machineID, signal);
4043
+ return {
4044
+ id: randomUUID(),
4045
+ region: this.options.region,
4046
+ credentials,
4047
+ refreshToken: raw.refresh_token,
4048
+ expiresAt: deviceExpiry(raw.expires_at, raw.expires_in) ?? Date.now() + 864e5,
4049
+ refreshExpiresAt: refreshExpiry(raw)
4050
+ };
4051
+ }
4052
+ };
4053
+ function refreshExpiry(raw) {
4054
+ return deviceExpiry(raw.refresh_token_expires_at, raw.refresh_token_expires_in);
4055
+ }
4056
+ /** Device-flow durations are seconds, including multi-day refresh-token lifetimes. */
4057
+ function deviceExpiry(absolute, seconds) {
4058
+ if (absolute !== void 0) {
4059
+ const parsed = typeof absolute === "string" ? Date.parse(absolute) : absolute > 0xe8d4a51000 ? absolute : absolute * 1e3;
4060
+ if (Number.isFinite(parsed)) return parsed;
4061
+ }
4062
+ if (typeof seconds === "number" && Number.isFinite(seconds * 1e3) && seconds > 0) return Date.now() + seconds * 1e3;
4063
+ }
4064
+ //#endregion
4065
+ //#region src/qoder/transport/wire/model-server-sse.ts
4066
+ function malformedFrameDetails(rawData, eventType, error) {
4067
+ const marker = /^\[[A-Z][A-Z_]{0,63}\]/u.exec(rawData)?.[0];
4068
+ const safeEventType = eventType && /^[a-z][\w.-]{0,40}$/iu.test(eventType) ? eventType : void 0;
4069
+ const message = error instanceof SyntaxError ? error.message : "";
4070
+ const offset = /\bposition (\d+)\b/u.exec(message)?.[1];
4071
+ const first = rawData.charAt(0);
4072
+ const last = rawData.charAt(rawData.length - 1);
4073
+ return {
4074
+ ...safeEventType === void 0 ? {} : { eventType: safeEventType },
4075
+ ...marker === void 0 ? {} : { marker },
4076
+ chars: rawData.length,
4077
+ lines: rawData.split("\n").length,
4078
+ shape: rawData.startsWith("data:") ? "nested-data" : first === "{" ? "object" : first === "[" ? "array" : first === "\"" ? "string" : "other",
4079
+ endsWith: last === "}" ? "object" : last === "]" ? "array" : last === "\"" ? "string" : "other",
4080
+ syntax: /end of JSON input|Unterminated string/u.test(message) || offset === String(rawData.length) ? "truncated" : /non-whitespace character after JSON/u.test(message) ? "trailing-data" : /control character/u.test(message) ? "invalid-control" : "invalid-json",
4081
+ ...offset === void 0 ? {} : { syntaxOffset: Number(offset) },
4082
+ ...rawData.includes("\r") ? { hasCarriageReturn: true } : {},
4083
+ ...rawData.includes("�") ? { hasReplacementCharacter: true } : {}
4084
+ };
4085
+ }
4086
+ function decodeModelServerFrame(rawData, options, eventType) {
4087
+ const controlKind = qoderControlFrameKind(rawData);
4088
+ if (controlKind !== void 0) {
4089
+ options.onControlFrame?.(controlKind);
4090
+ return;
4091
+ }
4092
+ let value;
4093
+ try {
4094
+ value = JSON.parse(rawData);
4095
+ } catch (error) {
4096
+ const details = malformedFrameDetails(rawData, eventType, error);
4097
+ options.onMalformedFrame?.(details);
4098
+ if (eventType === "error") throw qoderModelServerError(options.httpStatus ?? 200, { error: { message: "Qoder model-server emitted a non-JSON error SSE event." } }, "sse", options.headers);
4099
+ throw new QoderMalformedJsonFrameError(details.syntax === "truncated", /^\{\s*"choices"\s*:\s*\[\s*\]\s*[,}]/u.test(rawData));
4100
+ }
4101
+ if (eventType === "error") throw qoderModelServerError(options.httpStatus ?? 200, value, "sse", options.headers);
4102
+ if (value === null || typeof value !== "object") throw new QoderLlmError("Malformed model-server SSE frame.", "MALFORMED_RESPONSE");
4103
+ const payload = value;
4104
+ if (payload.error !== void 0) throw qoderModelServerError(options.httpStatus ?? 200, payload, "sse", options.headers);
4105
+ return payload;
4106
+ }
4107
+ function parseModelServerSse(stream, options = {}) {
4108
+ return parseQoderChunks(stream, decodeModelServerFrame, options, {
4109
+ ignoreMalformedAfterFinish: true,
4110
+ allowIncompleteAuxiliaryFrames: true,
4111
+ recoverUnprefixedContinuations: true
4112
+ });
4113
+ }
4114
+ //#endregion
4115
+ //#region src/qoder/transport/model-server.ts
4116
+ /** Bearer-authenticated model-server HTTP SSE transport. */
4117
+ /** Convert the shared Qoder turn into the model-server's OpenAI-shaped request. */
4118
+ function modelServerBody(request) {
4119
+ return {
4120
+ model: request.modelKey,
4121
+ messages: request.messages,
4122
+ stream: true,
4123
+ stream_options: { include_usage: true },
4124
+ metadata: {
4125
+ context: {
4126
+ request_id: request.requestId,
4127
+ request_set_id: request.requestSetId,
4128
+ session_id: request.sessionId,
4129
+ task_id: request.taskId,
4130
+ client_type: "5"
4131
+ },
4132
+ business: request.business
4133
+ },
4134
+ ...request.tools.length > 0 ? { tools: request.tools } : {},
4135
+ max_tokens: request.maxTokens,
4136
+ ...request.contextLength === void 0 ? {} : { context_length: request.contextLength },
4137
+ ...request.reasoningEffort === void 0 ? {} : { reasoning_effort: request.reasoningEffort }
4138
+ };
4139
+ }
4140
+ async function* streamModelServerChat(options, model, credentials, messages, dependencies) {
4141
+ const identityScope = options.purpose === void 0 && dependencies.hostRunId !== void 0 ? JSON.stringify([dependencies.region, dependencies.hostRunId]) : dependencies.region;
4142
+ const request = await prepareQoderRequest(options, credentials.userID, messages, model, void 0, void 0, identityScope);
4143
+ const body = JSON.stringify(modelServerBody(request));
4144
+ const startedAt = performance.now();
4145
+ dependencies.logger?.debug?.("[Qoder Stream] Request context", {
4146
+ requestId: request.requestId,
4147
+ model: request.modelKey,
4148
+ region: dependencies.region,
4149
+ purpose: options.purpose ?? "chat",
4150
+ transport: "http"
4151
+ });
4152
+ const controller = new AbortController();
4153
+ const onAbort = () => controller.abort(options.signal?.reason);
4154
+ options.signal?.addEventListener("abort", onAbort, { once: true });
4155
+ if (options.signal?.aborted) onAbort();
4156
+ let headerTimedOut = false;
4157
+ let idleTimedOut = false;
4158
+ let headerTimer;
4159
+ let idleTimer;
4160
+ const clearTimers = () => {
4161
+ if (headerTimer) clearTimeout(headerTimer);
4162
+ if (idleTimer) clearTimeout(idleTimer);
4163
+ };
4164
+ const resetIdle = () => {
4165
+ if (idleTimer) clearTimeout(idleTimer);
4166
+ idleTimer = setTimeout(() => {
4167
+ idleTimedOut = true;
4168
+ controller.abort("stream idle timeout");
4169
+ }, dependencies.streamIdleTimeoutMs);
4170
+ };
4171
+ try {
4172
+ const compression = new QoderRequestCompression();
4173
+ let activeCredentials = credentials;
4174
+ for (let attempt = 0; attempt < 2; attempt++) {
4175
+ if (controller.signal.aborted) throw new QoderLlmError("Qoder model request was aborted.", "ABORTED");
4176
+ let response;
4177
+ try {
4178
+ response = await compression.send({
4179
+ url: getQoderModelServerUrl(dependencies.region),
4180
+ protocol: "http",
4181
+ body,
4182
+ enabled: dependencies.requestCompression,
4183
+ signal: controller.signal,
4184
+ logger: dependencies.logger,
4185
+ requestId: request.requestId,
4186
+ onRejection: async (rejection) => {
4187
+ dependencies.logger?.warn?.("[Qoder Stream] Compression rejected", {
4188
+ requestId: request.requestId,
4189
+ status: rejection.status,
4190
+ contentType: rejection.contentType,
4191
+ serverRequestId: rejection.serverRequestId,
4192
+ bodyTruncated: rejection.bodyTruncated,
4193
+ reason: rejection.reason,
4194
+ request: compression.diagnostics
4195
+ });
4196
+ },
4197
+ dispatch: async (wireBody, headers) => {
4198
+ headerTimer = setTimeout(() => {
4199
+ headerTimedOut = true;
4200
+ controller.abort("response header timeout");
4201
+ }, dependencies.responseHeaderTimeoutMs);
4202
+ try {
4203
+ return await dependencies.fetch(getQoderModelServerUrl(dependencies.region), {
4204
+ method: "POST",
4205
+ headers,
4206
+ body: wireBody,
4207
+ signal: controller.signal
4208
+ });
4209
+ } finally {
4210
+ if (headerTimer) clearTimeout(headerTimer);
4211
+ }
4212
+ },
4213
+ headers: {
4214
+ ...attributionHeaders(),
4215
+ ...qoderModelClientHeaders(),
4216
+ "Accept": "text/event-stream",
4217
+ "Content-Type": "application/json",
4218
+ "Authorization": `Bearer ${activeCredentials.authToken}`,
4219
+ "X-Request-ID": request.requestId,
4220
+ "X-Session-ID": request.sessionId
4221
+ }
4222
+ });
4223
+ } finally {
4224
+ if (headerTimer) clearTimeout(headerTimer);
4225
+ }
4226
+ const responseRequestId = qoderRequestId(response.headers);
4227
+ dependencies.logger?.debug?.("[Qoder Stream] Response headers received", {
4228
+ region: dependencies.region,
4229
+ transport: "http",
4230
+ status: response.status,
4231
+ contentType: response.headers.get("content-type"),
4232
+ contentEncoding: response.headers.get("content-encoding"),
4233
+ attempt: attempt + 1,
4234
+ durationMs: Math.round(performance.now() - startedAt),
4235
+ ...responseRequestId === void 0 ? {} : { requestId: responseRequestId }
4236
+ });
4237
+ if (!response.ok) {
4238
+ let cause;
4239
+ resetIdle();
4240
+ try {
4241
+ cause = await readLimitedText(response, defaultMaxErrorBytes, "Qoder model-server error");
4242
+ } catch {
4243
+ cause = void 0;
4244
+ }
4245
+ if (idleTimer) clearTimeout(idleTimer);
4246
+ if (controller.signal.aborted) throw new QoderLlmError("Qoder model-server error body was interrupted.", "ABORTED");
4247
+ dependencies.logger?.warn?.("[Qoder Stream] HTTP request rejected", {
4248
+ requestId: request.requestId,
4249
+ status: response.status,
4250
+ contentType: response.headers.get("content-type"),
4251
+ serverRequestId: responseRequestId,
4252
+ request: compression.diagnostics,
4253
+ bodyAvailable: typeof cause === "string"
4254
+ });
4255
+ const error = qoderModelServerError(response.status, cause, "http", response.headers);
4256
+ if (attempt === 0 && canRefreshModelCredentials(error)) {
4257
+ activeCredentials = await dependencies.refreshCredentials(activeCredentials, options.signal);
4258
+ if (activeCredentials.userID !== credentials.userID) throw new QoderLlmError("Qoder subscriber identity changed during authentication recovery.", "AUTH");
4259
+ continue;
4260
+ }
4261
+ throw error;
4262
+ }
4263
+ if (!response.body) throw new QoderLlmError("Qoder model-server response has no body.", "EMPTY_RESPONSE");
4264
+ resetIdle();
4265
+ const streamStartedAt = performance.now();
4266
+ let chunkCount = 0;
4267
+ let firstChunkReceived = false;
4268
+ let finishReason;
4269
+ for await (const chunk of parseModelServerSse(response.body, {
4270
+ headers: response.headers,
4271
+ httpStatus: response.status,
4272
+ onActivity: resetIdle,
4273
+ onControlFrame: (kind) => {
4274
+ if (kind !== "done") dependencies.logger?.warn?.("[Qoder Stream] Control frame skipped", {
4275
+ transport: "http",
4276
+ kind,
4277
+ ...responseRequestId === void 0 ? {} : { requestId: responseRequestId }
4278
+ });
4279
+ },
4280
+ onMalformedFrame: (details) => dependencies.logger?.warn?.("[Qoder Stream] Malformed SSE frame", {
4281
+ transport: "http",
4282
+ ...details,
4283
+ ...responseRequestId === void 0 ? {} : { requestId: responseRequestId }
4284
+ })
4285
+ })) {
4286
+ compression.confirm();
4287
+ chunkCount++;
4288
+ if (!firstChunkReceived) {
4289
+ firstChunkReceived = true;
4290
+ dependencies.logger?.debug?.("[Qoder Stream] First chunk received", {
4291
+ transport: "http",
4292
+ durationMs: Math.round(performance.now() - startedAt),
4293
+ ...responseRequestId === void 0 ? {} : { requestId: responseRequestId }
4294
+ });
4295
+ }
4296
+ if (chunk.type === "finish") finishReason = chunk.reason.kind;
4297
+ yield chunk;
4298
+ }
4299
+ dependencies.logger?.debug?.("[Qoder Stream] Stream completed", {
4300
+ transport: "http",
4301
+ durationMs: Math.round(performance.now() - startedAt),
4302
+ streamDurationMs: Math.round(performance.now() - streamStartedAt),
4303
+ chunkCount,
4304
+ ...finishReason === void 0 ? {} : { finishReason },
4305
+ ...responseRequestId === void 0 ? {} : { requestId: responseRequestId }
4306
+ });
4307
+ return;
4308
+ }
4309
+ } catch (error) {
4310
+ if (options.signal?.aborted) throw new QoderLlmError("Qoder model request was aborted.", "ABORTED");
4311
+ if (headerTimedOut) throw new QoderLlmError("Qoder model-server response header timeout.", "TIMEOUT");
4312
+ if (idleTimedOut) throw new QoderLlmError("Qoder model-server stream idle timeout.", "TIMEOUT");
4313
+ throw error;
4314
+ } finally {
4315
+ clearTimers();
4316
+ options.signal?.removeEventListener("abort", onAbort);
4317
+ controller.abort("stream finished");
4318
+ }
4319
+ }
4320
+ //#endregion
3076
4321
  //#region src/qoder/transport/default-transport.ts
3077
4322
  const defaultStreamIdleTimeoutMs = 300 * 1e3;
3078
4323
  function aborted(message) {
3079
4324
  return new QoderLlmError(message, "ABORTED");
3080
4325
  }
3081
4326
  var DefaultQoderTransport = class {
4327
+ authorization;
4328
+ resolveCredential;
3082
4329
  region;
4330
+ requestCompression;
4331
+ modelTransport;
3083
4332
  resolvePat;
3084
4333
  fetchImpl;
3085
4334
  logger;
@@ -3091,19 +4340,34 @@ var DefaultQoderTransport = class {
3091
4340
  modelFlights = new SingleFlight();
3092
4341
  attachments;
3093
4342
  imageUploader;
3094
- searchClient;
4343
+ credentialLeases = /* @__PURE__ */ new WeakMap();
3095
4344
  preserveThinking;
4345
+ modelQueueMaxWaitMs;
4346
+ onQueueUpdate;
4347
+ captureQueueLeaseOwner;
3096
4348
  constructor(options) {
4349
+ this.modelQueueMaxWaitMs = options.modelQueueMaxWaitMs;
4350
+ this.onQueueUpdate = options.onQueueUpdate;
4351
+ this.captureQueueLeaseOwner = options.captureQueueLeaseOwner;
3097
4352
  this.region = options.region;
4353
+ this.requestCompression = options.requestCompression ?? false;
4354
+ this.modelTransport = options.modelTransport ?? "legacy";
3098
4355
  this.resolvePat = options.resolvePat;
4356
+ this.resolveCredential = options.resolveCredential;
4357
+ this.authorization = new QoderBrowserAuthorization(options);
3099
4358
  this.fetchImpl = options.fetch ?? globalThis.fetch;
3100
4359
  this.logger = options.logger;
4360
+ this.logger?.debug?.("[Qoder Transport] Request compression configuration", {
4361
+ implementation: "zstd-v1",
4362
+ node: process.version,
4363
+ requestCompression: this.requestCompression
4364
+ });
3101
4365
  this.streamIdleTimeoutMs = options.streamIdleTimeoutMs ?? 3e5;
3102
4366
  this.responseHeaderTimeoutMs = options.responseHeaderTimeoutMs ?? 6e4;
3103
4367
  this.metadataTimeoutMs = options.metadataTimeoutMs;
3104
4368
  this.attachments = options.attachments;
3105
4369
  this.preserveThinking = options.preserveThinking;
3106
- this.auth = new QoderAuthService({
4370
+ this.auth = new QoderAuthManager({
3107
4371
  fetch: this.fetchImpl,
3108
4372
  logger: this.logger,
3109
4373
  region: this.region,
@@ -3122,24 +4386,10 @@ var DefaultQoderTransport = class {
3122
4386
  region: this.region,
3123
4387
  ...options.imageUploadTimeoutMs === void 0 ? {} : { timeoutMs: options.imageUploadTimeoutMs },
3124
4388
  ...options.imageUrlCacheTtlMs === void 0 ? {} : { cacheTtlMs: options.imageUrlCacheTtlMs },
3125
- refreshCredentials: async (signal) => {
3126
- const pat = await this.requirePat(signal);
3127
- this.auth.clear(pat);
3128
- return this.auth.getCredentials(pat, signal);
3129
- }
3130
- });
3131
- this.searchClient = new QoderSearchClient({
3132
- fetch: this.fetchImpl,
3133
- logger: this.logger,
3134
- region: this.region,
3135
- resolveCredentials: async (signal) => {
3136
- const pat = await this.requirePat(signal);
3137
- return this.auth.getCredentials(pat, signal);
3138
- },
3139
- refreshCredentials: async (signal) => {
3140
- const pat = await this.requirePat(signal);
3141
- this.auth.clear(pat);
3142
- return this.auth.getCredentials(pat, signal);
4389
+ refreshCredentials: async (rejected, signal) => {
4390
+ const lease = rejected && this.credentialLeases.get(rejected);
4391
+ if (!lease) throw aborted("Qoder image credentials are no longer available.");
4392
+ return lease.resolve(signal, rejected.authToken);
3143
4393
  }
3144
4394
  });
3145
4395
  }
@@ -3147,28 +4397,54 @@ var DefaultQoderTransport = class {
3147
4397
  return this.generate(options, model);
3148
4398
  }
3149
4399
  async discoverModels(signal) {
3150
- const pat = await this.requirePat(signal);
3151
- const key = opaqueCredentialKey(pat);
4400
+ const lease = await this.lease(signal);
4401
+ const key = lease.key;
3152
4402
  return this.modelFlights.run(key, signal, async (sharedSignal) => {
3153
- const credentials = await this.auth.getCredentials(pat, sharedSignal);
3154
- return retryMetadataRead(sharedSignal, () => fetchQoderModels(credentials, {
4403
+ const credentials = await lease.resolve(sharedSignal);
4404
+ const read = (resolved) => retryMetadataRead(sharedSignal, () => fetchQoderModels(resolved, {
3155
4405
  fetch: this.fetchImpl,
3156
4406
  signal: sharedSignal,
3157
4407
  logger: this.logger,
3158
4408
  region: this.region,
3159
4409
  timeoutMs: this.metadataTimeoutMs
3160
4410
  }));
4411
+ try {
4412
+ return await read(credentials);
4413
+ } catch (error) {
4414
+ if (!(error instanceof QoderLlmError) || error.code !== "AUTH") throw error;
4415
+ return read(await lease.resolve(sharedSignal, credentials.authToken));
4416
+ }
3161
4417
  }, () => aborted("Qoder model discovery was aborted."));
3162
4418
  }
3163
4419
  async readAccount(options) {
3164
- const pat = await this.requirePat(options?.signal);
3165
- return this.usage.readAccount(pat, {
3166
- force: options?.force,
4420
+ const lease = await this.lease(options?.signal);
4421
+ let used;
4422
+ const read = (force = options?.force) => this.usage.readWithCredentials(lease.key, async (signal) => {
4423
+ used = await lease.resolve(signal);
4424
+ return used;
4425
+ }, {
4426
+ force,
3167
4427
  signal: options?.signal
3168
4428
  });
4429
+ try {
4430
+ return await read();
4431
+ } catch (error) {
4432
+ if (!(error instanceof QoderLlmError) || error.code !== "AUTH" || !used) throw error;
4433
+ await lease.resolve(options?.signal, used.authToken);
4434
+ return read(true);
4435
+ }
3169
4436
  }
3170
4437
  async searchWeb(request, signal) {
3171
- return this.searchClient.search(request, signal);
4438
+ const lease = await this.lease(signal);
4439
+ return new QoderSearchClient({
4440
+ fetch: this.fetchImpl,
4441
+ logger: this.logger,
4442
+ region: this.region,
4443
+ resolveCredentials: async (nextSignal) => {
4444
+ return lease.resolve(nextSignal);
4445
+ },
4446
+ refreshCredentials: (rejected, nextSignal) => lease.resolve(nextSignal, rejected.authToken)
4447
+ }).search(request, signal);
3172
4448
  }
3173
4449
  async requirePat(signal) {
3174
4450
  if (signal?.aborted) throw aborted("Qoder request was aborted.");
@@ -3177,27 +4453,50 @@ var DefaultQoderTransport = class {
3177
4453
  if (signal?.aborted) throw aborted("Qoder request was aborted.");
3178
4454
  return pat;
3179
4455
  }
4456
+ async lease(signal) {
4457
+ if (signal?.aborted) throw aborted("Qoder request was aborted.");
4458
+ const managed = await this.resolveCredential?.(signal);
4459
+ if (managed) return managed;
4460
+ const pat = await this.requirePat(signal);
4461
+ return {
4462
+ key: opaqueCredentialKey(pat),
4463
+ resolve: async (nextSignal, rejectedToken) => {
4464
+ const current = await this.auth.getCredentials(pat, nextSignal);
4465
+ if (rejectedToken !== void 0 && current.authToken === rejectedToken) return this.auth.refreshCredentials(pat, current, nextSignal);
4466
+ return current;
4467
+ }
4468
+ };
4469
+ }
3180
4470
  async *generate(options, model) {
3181
4471
  if (options.signal?.aborted) throw aborted("Request was aborted prior to generation.");
4472
+ const queueOwner = options.sessionId === void 0 ? void 0 : this.captureQueueLeaseOwner?.(String(options.sessionId));
3182
4473
  validateQoderRequestShape(options, model);
3183
- const pat = await this.requirePat(options.signal);
3184
- const credentials = await this.auth.getCredentials(pat, options.signal);
4474
+ const lease = await this.lease(options.signal);
4475
+ const credentials = await lease.resolve(options.signal);
4476
+ this.credentialLeases.set(credentials, lease);
3185
4477
  const messages = await translateQoderMessages(options, this.attachments, {
3186
4478
  uploader: this.imageUploader,
3187
4479
  credentials,
3188
4480
  preserveThinking: this.preserveThinking
3189
4481
  });
3190
- yield* streamQoderChat(options, model, await this.auth.getCredentials(pat, options.signal), messages, {
4482
+ const chatCredentials = await lease.resolve(options.signal);
4483
+ yield* (this.modelTransport === "legacy" ? streamQoderChat : streamModelServerChat)(options, model, chatCredentials, messages, {
3191
4484
  fetch: this.fetchImpl,
3192
4485
  logger: this.logger,
3193
4486
  region: this.region,
4487
+ requestCompression: this.requestCompression,
3194
4488
  responseHeaderTimeoutMs: this.responseHeaderTimeoutMs,
3195
4489
  streamIdleTimeoutMs: this.streamIdleTimeoutMs,
4490
+ modelQueueMaxWaitMs: this.modelQueueMaxWaitMs,
4491
+ onQueueUpdate: this.onQueueUpdate,
4492
+ hostRunId: queueOwner?.runId,
4493
+ onQueueLease: queueOwner?.retain,
4494
+ resolveCredentials: async (refresh, signal) => {
4495
+ const current = await lease.resolve(signal);
4496
+ return refresh ? lease.resolve(signal, current.authToken) : current;
4497
+ },
3196
4498
  refreshCredentials: async (rejected, signal) => {
3197
- const current = await this.auth.getCredentials(pat, signal);
3198
- if (current.authToken !== rejected.authToken) return current;
3199
- this.auth.clear(pat);
3200
- return this.auth.getCredentials(pat, signal);
4499
+ return lease.resolve(signal, rejected.authToken);
3201
4500
  }
3202
4501
  });
3203
4502
  }
@@ -3238,6 +4537,7 @@ const catalogModel = z.object({
3238
4537
  });
3239
4538
  const modelsByRegionSchema = z.dict(z.array(catalogModel));
3240
4539
  const Config = z.object({
4540
+ modelQueueMaxWaitMs: z.number().step(1).min(1).max(2147483647).default(defaultModelQueueMaxWaitMs).description("Maximum cumulative model queue wait in milliseconds; changes apply to new calls"),
3241
4541
  region: z.union(["global", "china"]).default("global"),
3242
4542
  modelsByRegion: modelsByRegionSchema.default({}),
3243
4543
  streamIdleTimeoutMs: z.number().step(1).min(1).default(defaultStreamIdleTimeoutMs),
@@ -3247,7 +4547,9 @@ const Config = z.object({
3247
4547
  "auto",
3248
4548
  "always",
3249
4549
  "disabled"
3250
- ]).default("auto")
4550
+ ]).default("auto"),
4551
+ requestCompression: z.boolean().default(false),
4552
+ modelTransport: z.union(["http", "legacy"]).default("legacy")
3251
4553
  });
3252
4554
  const PluginConfig = typeof Config.volatile === "function" ? Config.volatile() : Config;
3253
4555
  function readConfig(value) {
@@ -3268,6 +4570,84 @@ function modelsFor(config, region) {
3268
4570
  return resolveModels(config.modelsByRegion?.[region]);
3269
4571
  }
3270
4572
  //#endregion
4573
+ //#region src/dsh/queue-leases.ts
4574
+ /** Correlate transport-owned release actions with committed DSH turn boundaries. */
4575
+ /** Bind committed boundaries and recover ownership when loaded during an open turn. */
4576
+ function bindQoderQueueLeases(ctx, leases) {
4577
+ ctx.on("session/event", (session, event) => {
4578
+ if (event.type === "turn/start") leases.start(session.id, event.data.turn);
4579
+ if (event.type === "turn/end") leases.end(session.id, event.data.turn);
4580
+ });
4581
+ ctx.on("agent/request", async ({ agent, turn }, next) => {
4582
+ leases.start(agent.session.id, turn);
4583
+ return next();
4584
+ });
4585
+ }
4586
+ var QoderQueueLeases = class {
4587
+ runs = /* @__PURE__ */ new Map();
4588
+ pending = /* @__PURE__ */ new Set();
4589
+ disposed = false;
4590
+ start(sessionId, turn) {
4591
+ if (this.disposed || this.runs.get(sessionId)?.turn === turn) return;
4592
+ this.end(sessionId);
4593
+ this.runs.set(sessionId, {
4594
+ turn,
4595
+ runId: randomUUID(),
4596
+ leases: /* @__PURE__ */ new Map()
4597
+ });
4598
+ }
4599
+ retain = (lease) => {
4600
+ const run = this.runs.get(lease.sessionId);
4601
+ if (this.disposed || !run) return false;
4602
+ if (!run.leases.has(lease.key)) run.leases.set(lease.key, lease);
4603
+ return true;
4604
+ };
4605
+ /** Bind ownership before provider I/O; a late old call cannot join a newer turn. */
4606
+ capture = (sessionId) => {
4607
+ const owner = this.runs.get(sessionId);
4608
+ if (!owner || this.disposed) return void 0;
4609
+ return {
4610
+ runId: owner.runId,
4611
+ retain: (lease) => lease.sessionId === sessionId && this.runs.get(sessionId) === owner && this.retain(lease)
4612
+ };
4613
+ };
4614
+ async end(sessionId, turn) {
4615
+ const run = this.runs.get(sessionId);
4616
+ if (!run || turn !== void 0 && turn !== run.turn) return;
4617
+ this.runs.delete(sessionId);
4618
+ const cleanup = Promise.allSettled([...run.leases.values()].map((lease) => Promise.resolve().then(() => lease.release())));
4619
+ this.pending.add(cleanup);
4620
+ try {
4621
+ await cleanup;
4622
+ } finally {
4623
+ this.pending.delete(cleanup);
4624
+ }
4625
+ }
4626
+ async dispose() {
4627
+ this.disposed = true;
4628
+ await Promise.allSettled([...this.runs.keys()].map((id) => this.end(id)));
4629
+ await Promise.allSettled([...this.pending]);
4630
+ }
4631
+ };
4632
+ //#endregion
4633
+ //#region src/dsh/queue-status.ts
4634
+ var QoderQueueStatusStore = class {
4635
+ calls = /* @__PURE__ */ new Map();
4636
+ active = true;
4637
+ update = (event) => {
4638
+ if (!this.active) return;
4639
+ if (event.type === "clear") this.calls.delete(event.callId);
4640
+ else this.calls.set(event.snapshot.callId, { ...event.snapshot });
4641
+ };
4642
+ read(sessionId) {
4643
+ return [...this.calls.values()].filter((value) => value.sessionId === sessionId).map((value) => ({ ...value }));
4644
+ }
4645
+ dispose() {
4646
+ this.active = false;
4647
+ this.calls.clear();
4648
+ }
4649
+ };
4650
+ //#endregion
3271
4651
  //#region src/dsh/catalog-lifecycle.ts
3272
4652
  /** Region-scoped Qoder model discovery, effective catalogs, and advisory persistence. */
3273
4653
  var QoderCatalogLifecycle = class {
@@ -3317,6 +4697,16 @@ var QoderCatalogLifecycle = class {
3317
4697
  const region = this.options.region();
3318
4698
  return mergeQoderDiscoveryMetadata(this.options.configuredModels(region), this.catalogs[region].models);
3319
4699
  }
4700
+ /** Subscriber replacement must not retain another subscriber's discovered capabilities. */
4701
+ clearDiscovered() {
4702
+ for (const state of Object.values(this.catalogs)) {
4703
+ state.models = [];
4704
+ state.generation++;
4705
+ state.expiresAt = 0;
4706
+ state.inflight = void 0;
4707
+ state.dirty = false;
4708
+ }
4709
+ }
3320
4710
  /** Automatic reads join the newest discovery and never wait for settings writes. */
3321
4711
  async refresh() {
3322
4712
  while (!this.disposed) {
@@ -3695,9 +5085,14 @@ const qoderRpcApiPath = `/api/qoder-subscription`;
3695
5085
  const qoderRpcEndpoints = [
3696
5086
  "account",
3697
5087
  "models",
5088
+ "queue",
3698
5089
  "sessionTier",
3699
5090
  "readSessionTier",
3700
- "sessionTierEvents"
5091
+ "sessionTierEvents",
5092
+ "authorization",
5093
+ "beginLogin",
5094
+ "cancelLogin",
5095
+ "selectAuthorization"
3701
5096
  ];
3702
5097
  /** Absolute Fetch-route path of one endpoint. */
3703
5098
  function qoderRpcPath(endpoint) {
@@ -3809,6 +5204,296 @@ function sessionTierEvents(request, subscribe, streams) {
3809
5204
  } });
3810
5205
  }
3811
5206
  //#endregion
5207
+ //#region src/dsh/browser-authorization.ts
5208
+ /** Instance-shared authorization lifecycle. Provider I/O is delegated to Qoder transport. */
5209
+ const browserAuthorizationKey = (region) => credentialKey("provider-qoder", region);
5210
+ function decode(record, region) {
5211
+ if (record === void 0) return {
5212
+ version: 1,
5213
+ method: "pat"
5214
+ };
5215
+ const value = record.kind === "grant" ? record.payload : null;
5216
+ if (!value || value.version !== 1 || ![
5217
+ "pat",
5218
+ "browser",
5219
+ "none"
5220
+ ].includes(value.method ?? "")) throw new QoderLlmError("Saved Qoder authorization is invalid. Remove it and sign in again.", "AUTH");
5221
+ if (value.grant) {
5222
+ const grant = value.grant;
5223
+ if (grant.region !== region || typeof grant.id !== "string" || !grant.id || typeof grant.refreshToken !== "string" || !grant.refreshToken || !Number.isFinite(grant.expiresAt) || !grant.credentials || typeof grant.credentials.userID !== "string" || !grant.credentials.userID || typeof grant.credentials.authToken !== "string" || !grant.credentials.authToken) throw new QoderLlmError("Saved Qoder browser authorization is invalid.", "AUTH");
5224
+ }
5225
+ return value;
5226
+ }
5227
+ const record = (payload) => ({
5228
+ kind: "grant",
5229
+ payload: JSON.parse(JSON.stringify(payload))
5230
+ });
5231
+ const pending = (attempt) => attempt !== void 0 && [
5232
+ "waiting",
5233
+ "verifying",
5234
+ "saving"
5235
+ ].includes(attempt.phase);
5236
+ const cancelled = () => new QoderLlmError("Qoder authorization changed or was cancelled.", "ABORTED");
5237
+ var QoderBrowserAuthorizationManager = class {
5238
+ credentials;
5239
+ transport;
5240
+ changed;
5241
+ attempts = /* @__PURE__ */ new Map();
5242
+ volatile = /* @__PURE__ */ new Map();
5243
+ invalid = /* @__PURE__ */ new Set();
5244
+ renewals = /* @__PURE__ */ new Set();
5245
+ lifecycle = new AbortController();
5246
+ activeRegion;
5247
+ regionGeneration = 0;
5248
+ disposed = false;
5249
+ constructor(options) {
5250
+ this.credentials = options.credentials;
5251
+ this.transport = options.transport;
5252
+ this.changed = options.changed;
5253
+ this.activeRegion = options.region;
5254
+ }
5255
+ activateRegion(region) {
5256
+ if (this.activeRegion === region) return;
5257
+ const previous = this.activeRegion;
5258
+ this.activeRegion = region;
5259
+ this.regionGeneration++;
5260
+ if (previous) this.cancel(previous).catch(() => {});
5261
+ }
5262
+ assertRegion(region, generation) {
5263
+ if (this.disposed || this.regionGeneration !== generation || this.activeRegion !== void 0 && this.activeRegion !== region) throw cancelled();
5264
+ }
5265
+ async read(region) {
5266
+ return decode(await this.credentials.readRecord?.(browserAuthorizationKey(region)), region);
5267
+ }
5268
+ async describe(region) {
5269
+ const writable = await this.credentials.describeRecord?.(browserAuthorizationKey(region));
5270
+ let saved;
5271
+ try {
5272
+ saved = await this.read(region);
5273
+ } catch (error) {
5274
+ if (!(error instanceof QoderLlmError) || error.code !== "AUTH") throw error;
5275
+ return {
5276
+ region,
5277
+ method: "none",
5278
+ configured: false,
5279
+ writable: writable?.writable ?? false,
5280
+ expired: true
5281
+ };
5282
+ }
5283
+ const live = this.volatile.get(region);
5284
+ const grant = live && live.grant.id === saved.grant?.id && live.baseToken === saved.grant?.credentials.authToken ? live.grant : saved.grant;
5285
+ const attempt = this.attempts.get(region);
5286
+ const expired = grant !== void 0 && (saved.expired === true || this.invalid.has(grant.id) || grant.refreshExpiresAt !== void 0 && grant.refreshExpiresAt <= Date.now());
5287
+ return {
5288
+ region,
5289
+ method: saved.method,
5290
+ writable: writable?.writable ?? false,
5291
+ configured: saved.method === "pat" ? Boolean(await resolveManagedQoderPat(this.credentials)) : saved.method === "browser" && grant !== void 0 && !expired,
5292
+ ...grant ? {
5293
+ profile: {
5294
+ id: grant.credentials.userID,
5295
+ name: grant.credentials.name,
5296
+ email: grant.credentials.email
5297
+ },
5298
+ expired: saved.method === "browser" && expired,
5299
+ unsaved: live?.grant === grant
5300
+ } : {},
5301
+ ...attempt ? { attempt: {
5302
+ id: attempt.id,
5303
+ phase: attempt.phase,
5304
+ expiresAt: attempt.expiresAt,
5305
+ ...attempt.failure ? { failure: attempt.failure } : {},
5306
+ ...pending(attempt) ? { url: attempt.url } : {}
5307
+ } } : {}
5308
+ };
5309
+ }
5310
+ async begin(region) {
5311
+ const generation = this.regionGeneration;
5312
+ await this.assertWritable(region);
5313
+ this.assertRegion(region, generation);
5314
+ if (pending(this.attempts.get(region))) return this.describe(region);
5315
+ const attempt = {
5316
+ id: randomUUID(),
5317
+ phase: "waiting",
5318
+ controller: new AbortController(),
5319
+ expiresAt: Date.now() + 3e5
5320
+ };
5321
+ this.attempts.set(region, attempt);
5322
+ attempt.done = this.login(region, attempt);
5323
+ await Promise.resolve();
5324
+ return this.describe(region);
5325
+ }
5326
+ async login(region, attempt) {
5327
+ let previous;
5328
+ let installed;
5329
+ try {
5330
+ const flow = await this.transport(region).begin(attempt.controller.signal);
5331
+ this.assertAttempt(region, attempt);
5332
+ attempt.url = flow.url;
5333
+ attempt.expiresAt = flow.expiresAt;
5334
+ const grant = await flow.complete();
5335
+ this.assertAttempt(region, attempt);
5336
+ attempt.phase = "saving";
5337
+ await this.credentials.modifyRecord(browserAuthorizationKey(region), async (current) => {
5338
+ this.assertAttempt(region, attempt);
5339
+ previous = current;
5340
+ installed = grant.id;
5341
+ return record({
5342
+ version: 1,
5343
+ method: "browser",
5344
+ grant
5345
+ });
5346
+ });
5347
+ this.assertAttempt(region, attempt);
5348
+ this.volatile.delete(region);
5349
+ attempt.phase = "complete";
5350
+ this.changed();
5351
+ } catch (error) {
5352
+ attempt.failure = attempt.phase === "saving" ? "storage" : error instanceof QoderLlmError && [
5353
+ "TRANSPORT",
5354
+ "SERVER",
5355
+ "TIMEOUT"
5356
+ ].includes(error.code) ? "connection" : "authorization";
5357
+ if (installed && attempt.controller.signal.aborted) await this.credentials.modifyRecord(browserAuthorizationKey(region), async (current) => {
5358
+ if (decode(current, region).grant?.id !== installed) return void 0;
5359
+ return previous ?? record({
5360
+ version: 1,
5361
+ method: "pat"
5362
+ });
5363
+ }).catch(() => {
5364
+ attempt.cleanupFailed = true;
5365
+ });
5366
+ attempt.phase = attempt.cleanupFailed ? "failed" : attempt.controller.signal.aborted ? "cancelled" : error instanceof QoderLlmError && error.code === "TIMEOUT" && Date.now() >= attempt.expiresAt ? "timeout" : "failed";
5367
+ } finally {
5368
+ attempt.url = void 0;
5369
+ }
5370
+ }
5371
+ assertAttempt(region, attempt) {
5372
+ if (this.disposed || attempt.controller.signal.aborted || this.attempts.get(region) !== attempt) throw cancelled();
5373
+ }
5374
+ async cancel(region) {
5375
+ const attempt = this.attempts.get(region);
5376
+ if (!pending(attempt)) return;
5377
+ attempt.controller.abort();
5378
+ await attempt.done;
5379
+ if (attempt.cleanupFailed) throw new QoderLlmError("Could not restore the previous Qoder authorization after cancellation. Reload account status.", "INVALID_REQUEST");
5380
+ }
5381
+ async select(region, method) {
5382
+ const generation = this.regionGeneration;
5383
+ await this.assertWritable(region);
5384
+ await this.cancel(region);
5385
+ await this.credentials.modifyRecord(browserAuthorizationKey(region), async (current) => {
5386
+ this.assertRegion(region, generation);
5387
+ const saved = method === "none" ? {
5388
+ version: 1,
5389
+ method
5390
+ } : decode(current, region);
5391
+ if (method === "browser" && !saved.grant) throw new QoderLlmError("Sign in to Qoder first.", "MISSING_CREDENTIAL");
5392
+ return record(method === "none" ? {
5393
+ version: 1,
5394
+ method
5395
+ } : {
5396
+ ...saved,
5397
+ method
5398
+ });
5399
+ });
5400
+ if (method === "none") this.volatile.delete(region);
5401
+ this.attempts.delete(region);
5402
+ this.changed();
5403
+ return this.describe(region);
5404
+ }
5405
+ async lease(region, signal) {
5406
+ if (signal?.aborted || this.disposed) throw cancelled();
5407
+ const saved = await this.read(region);
5408
+ if (saved.method === "pat") return void 0;
5409
+ const grant = saved.grant;
5410
+ if (saved.method !== "browser" || !grant) throw new QoderLlmError("Sign in to Qoder or select a Managed Qoder PAT.", "MISSING_CREDENTIAL");
5411
+ if (saved.expired || this.invalid.has(grant.id)) throw new QoderLlmError("Qoder browser authorization expired. Sign in again.", "AUTH");
5412
+ let captured = grant;
5413
+ return {
5414
+ key: `browser:${grant.id}`,
5415
+ resolve: async (nextSignal, rejectedToken) => {
5416
+ if (nextSignal?.aborted || this.disposed) throw cancelled();
5417
+ const latest = await this.read(region);
5418
+ if (latest.method !== "browser" || latest.grant?.id !== grant.id) {
5419
+ if (rejectedToken !== void 0 || captured.expiresAt <= Date.now()) throw cancelled();
5420
+ return captured.credentials;
5421
+ }
5422
+ const combined = nextSignal ? AbortSignal.any([nextSignal, this.lifecycle.signal]) : this.lifecycle.signal;
5423
+ const renewal = this.renew(region, grant.id, combined, rejectedToken);
5424
+ this.renewals.add(renewal);
5425
+ try {
5426
+ captured = await renewal;
5427
+ } finally {
5428
+ this.renewals.delete(renewal);
5429
+ }
5430
+ if (combined.aborted) throw cancelled();
5431
+ return captured.credentials;
5432
+ }
5433
+ };
5434
+ }
5435
+ async renew(region, id, signal, rejectedToken) {
5436
+ if (this.invalid.has(id)) throw new QoderLlmError("Qoder browser authorization expired. Sign in again.", "AUTH");
5437
+ const snapshot = await this.read(region);
5438
+ const fresh = snapshot.method === "browser" && snapshot.grant?.id === id ? snapshot.grant : void 0;
5439
+ if (fresh && !snapshot.expired && !this.volatile.has(region) && fresh.expiresAt > Date.now() + 3e5 && rejectedToken !== fresh.credentials.authToken) return fresh;
5440
+ let rotated;
5441
+ let baseToken;
5442
+ try {
5443
+ await this.credentials.modifyRecord(browserAuthorizationKey(region), async (current) => {
5444
+ const saved = decode(current, region);
5445
+ if (this.disposed || signal?.aborted || saved.method !== "browser" || saved.grant?.id !== id) throw cancelled();
5446
+ if (saved.expired) throw new QoderLlmError("Qoder browser authorization expired. Sign in again.", "AUTH");
5447
+ const volatile = this.volatile.get(region);
5448
+ baseToken = saved.grant.credentials.authToken;
5449
+ const source = volatile?.grant.id === id && volatile.baseToken === baseToken ? volatile.grant : saved.grant;
5450
+ rotated = await this.transport(region).refresh(source, this.lifecycle.signal, rejectedToken);
5451
+ return rotated === saved.grant ? void 0 : record({
5452
+ ...saved,
5453
+ grant: rotated
5454
+ });
5455
+ });
5456
+ this.volatile.delete(region);
5457
+ return rotated;
5458
+ } catch (error) {
5459
+ if (error instanceof QoderLlmError && error.code === "AUTH") {
5460
+ this.invalid.add(id);
5461
+ await this.credentials.modifyRecord(browserAuthorizationKey(region), async (current) => {
5462
+ const saved = decode(current, region);
5463
+ return saved.grant?.id === id ? record({
5464
+ ...saved,
5465
+ expired: true
5466
+ }) : void 0;
5467
+ }).catch(() => {});
5468
+ }
5469
+ if (rotated && baseToken) {
5470
+ const saved = await this.read(region);
5471
+ if (saved.method === "browser" && saved.grant?.id === id && saved.grant.credentials.authToken === rotated.credentials.authToken) return saved.grant;
5472
+ if (saved.method === "browser" && saved.grant?.id === id && saved.grant.credentials.authToken === baseToken) {
5473
+ this.volatile.set(region, {
5474
+ baseToken,
5475
+ grant: rotated
5476
+ });
5477
+ return rotated;
5478
+ }
5479
+ }
5480
+ throw error;
5481
+ }
5482
+ }
5483
+ async assertWritable(region) {
5484
+ if (!(await this.credentials.describeRecord?.(browserAuthorizationKey(region)))?.writable) throw new QoderLlmError("Qoder authorization storage is read-only or unsupported.", "INVALID_REQUEST");
5485
+ }
5486
+ async dispose() {
5487
+ this.disposed = true;
5488
+ this.lifecycle.abort();
5489
+ for (const attempt of this.attempts.values()) attempt.controller.abort();
5490
+ await Promise.all([...this.attempts.values()].map((attempt) => attempt.done));
5491
+ await Promise.allSettled(this.renewals);
5492
+ this.volatile.clear();
5493
+ this.invalid.clear();
5494
+ }
5495
+ };
5496
+ //#endregion
3812
5497
  //#region src/dsh/plugin.ts
3813
5498
  const name = "provider-qoder";
3814
5499
  const inject = [
@@ -3860,25 +5545,43 @@ async function executeRpc(operation, task, signal, logger) {
3860
5545
  }
3861
5546
  }
3862
5547
  function apply(ctx, input = {}) {
5548
+ const leases = new QoderQueueLeases();
5549
+ const queueStatus = new QoderQueueStatusStore();
5550
+ ctx.effect(() => async () => {
5551
+ queueStatus.dispose();
5552
+ await leases.dispose();
5553
+ }, "provider-qoder: queue lifecycle");
5554
+ bindQoderQueueLeases(ctx, leases);
3863
5555
  const config = readConfig(input);
3864
5556
  const logger = ctx.logger;
5557
+ const initialRegion = config.region ?? "global";
3865
5558
  const baseConfig = {
3866
- region: config.region ?? "global",
5559
+ region: initialRegion,
3867
5560
  modelsByRegion: { ...config.modelsByRegion },
3868
5561
  streamIdleTimeoutMs: config.streamIdleTimeoutMs ?? 3e5,
3869
5562
  responseHeaderTimeoutMs: config.responseHeaderTimeoutMs ?? 6e4,
5563
+ modelQueueMaxWaitMs: config.modelQueueMaxWaitMs ?? 36e5,
3870
5564
  preserveThinking: config.preserveThinking ?? true,
3871
- webSearchMode: config.webSearchMode ?? "auto"
5565
+ webSearchMode: config.webSearchMode ?? "auto",
5566
+ requestCompression: config.requestCompression ?? false,
5567
+ modelTransport: config.modelTransport ?? "legacy"
3872
5568
  };
3873
5569
  let current = () => baseConfig;
3874
- const createTransport = (region, streamIdleTimeoutMs, responseHeaderTimeoutMs, preserveThinking, resolvePat = () => resolveManagedQoderPat(ctx.credentials)) => createQoderTransport({
5570
+ let authorization;
5571
+ const createTransport = (region, streamIdleTimeoutMs, responseHeaderTimeoutMs, preserveThinking, resolvePat) => createQoderTransport({
3875
5572
  region,
3876
- resolvePat,
5573
+ resolvePat: resolvePat ?? (() => resolveManagedQoderPat(ctx.credentials)),
5574
+ resolveCredential: resolvePat ? void 0 : (signal) => authorization.lease(region, signal),
5575
+ requestCompression: current().requestCompression ?? false,
5576
+ modelTransport: current().modelTransport ?? "legacy",
3877
5577
  logger,
3878
5578
  streamIdleTimeoutMs,
3879
5579
  responseHeaderTimeoutMs,
3880
5580
  attachments: ctx.attachments,
3881
- preserveThinking
5581
+ preserveThinking,
5582
+ modelQueueMaxWaitMs: current().modelQueueMaxWaitMs ?? 36e5,
5583
+ onQueueUpdate: queueStatus.update,
5584
+ captureQueueLeaseOwner: leases.capture
3882
5585
  });
3883
5586
  const resolveConfig = () => {
3884
5587
  const value = current();
@@ -3888,8 +5591,11 @@ function apply(ctx, input = {}) {
3888
5591
  models: modelsFor(value, region),
3889
5592
  streamIdleTimeoutMs: value.streamIdleTimeoutMs ?? 3e5,
3890
5593
  responseHeaderTimeoutMs: value.responseHeaderTimeoutMs ?? 6e4,
5594
+ modelQueueMaxWaitMs: value.modelQueueMaxWaitMs ?? 36e5,
3891
5595
  preserveThinking: value.preserveThinking ?? true,
3892
- webSearchMode: value.webSearchMode ?? "auto"
5596
+ webSearchMode: value.webSearchMode ?? "auto",
5597
+ requestCompression: value.requestCompression ?? false,
5598
+ modelTransport: value.modelTransport ?? "legacy"
3893
5599
  };
3894
5600
  };
3895
5601
  const initial = resolveConfig();
@@ -3898,8 +5604,31 @@ function apply(ctx, input = {}) {
3898
5604
  region: initial.region,
3899
5605
  streamIdleTimeoutMs: initial.streamIdleTimeoutMs,
3900
5606
  responseHeaderTimeoutMs: initial.responseHeaderTimeoutMs,
3901
- preserveThinking: initial.preserveThinking
5607
+ modelQueueMaxWaitMs: initial.modelQueueMaxWaitMs,
5608
+ preserveThinking: initial.preserveThinking,
5609
+ requestCompression: initial.requestCompression,
5610
+ modelTransport: initial.modelTransport
3902
5611
  };
5612
+ const authorizationTransports = /* @__PURE__ */ new Map();
5613
+ authorization = new QoderBrowserAuthorizationManager({
5614
+ credentials: ctx.credentials,
5615
+ region: initialRegion,
5616
+ transport: (region) => {
5617
+ let transport = authorizationTransports.get(region);
5618
+ if (!transport) {
5619
+ transport = createQoderTransport({
5620
+ region,
5621
+ resolvePat: () => Promise.resolve("")
5622
+ }).authorization;
5623
+ authorizationTransports.set(region, transport);
5624
+ }
5625
+ return transport;
5626
+ },
5627
+ changed: () => {
5628
+ catalog.refresh();
5629
+ }
5630
+ });
5631
+ ctx.effect(() => () => authorization.dispose(), "provider-qoder: browser authorization lifecycle");
3903
5632
  const catalog = new QoderCatalogLifecycle({
3904
5633
  resolveTransport: () => activeTransport,
3905
5634
  region: () => activeTransportConfig.region,
@@ -3918,15 +5647,35 @@ function apply(ctx, input = {}) {
3918
5647
  agents: ctx.get("agents")
3919
5648
  });
3920
5649
  const registration = ctx.llm.registerAdapter([providerQoder], adapter);
5650
+ const refreshAuthentication = () => {
5651
+ const value = resolveConfig();
5652
+ activeTransport = createTransport(value.region, value.streamIdleTimeoutMs, value.responseHeaderTimeoutMs, value.preserveThinking);
5653
+ catalog.clearDiscovered();
5654
+ catalog.observeTransport();
5655
+ registration.replace([providerQoder]);
5656
+ };
5657
+ ctx.on("credentials/record-updated", (key) => {
5658
+ if (key === browserAuthorizationKey(activeTransportConfig.region)) {
5659
+ refreshAuthentication();
5660
+ catalog.refresh();
5661
+ }
5662
+ });
5663
+ ctx.on("credentials/reference-updated", (ref) => {
5664
+ if (ref === "QODER_MANAGED_CREDENTIAL") refreshAuthentication();
5665
+ });
3921
5666
  const refreshAdapter = () => {
3922
5667
  const next = resolveConfig();
3923
- if (next.region !== activeTransportConfig.region || next.streamIdleTimeoutMs !== activeTransportConfig.streamIdleTimeoutMs || next.responseHeaderTimeoutMs !== activeTransportConfig.responseHeaderTimeoutMs || next.preserveThinking !== activeTransportConfig.preserveThinking) {
5668
+ if (next.region !== activeTransportConfig.region || next.streamIdleTimeoutMs !== activeTransportConfig.streamIdleTimeoutMs || next.responseHeaderTimeoutMs !== activeTransportConfig.responseHeaderTimeoutMs || next.modelQueueMaxWaitMs !== activeTransportConfig.modelQueueMaxWaitMs || next.requestCompression !== activeTransportConfig.requestCompression || next.modelTransport !== activeTransportConfig.modelTransport || next.preserveThinking !== activeTransportConfig.preserveThinking) {
5669
+ authorization.activateRegion(next.region);
3924
5670
  activeTransport = createTransport(next.region, next.streamIdleTimeoutMs, next.responseHeaderTimeoutMs, next.preserveThinking);
3925
5671
  activeTransportConfig = {
3926
5672
  region: next.region,
3927
5673
  streamIdleTimeoutMs: next.streamIdleTimeoutMs,
3928
5674
  responseHeaderTimeoutMs: next.responseHeaderTimeoutMs,
3929
- preserveThinking: next.preserveThinking
5675
+ modelQueueMaxWaitMs: next.modelQueueMaxWaitMs,
5676
+ preserveThinking: next.preserveThinking,
5677
+ requestCompression: next.requestCompression,
5678
+ modelTransport: next.modelTransport
3930
5679
  };
3931
5680
  }
3932
5681
  catalog.observeTransport();
@@ -3982,12 +5731,48 @@ function apply(ctx, input = {}) {
3982
5731
  const handler = async (endpoint, payload, signal) => {
3983
5732
  if (!isQoderRpcEndpoint(endpoint)) return publicError("UNKNOWN_ENDPOINT", `Unknown endpoint: ${endpoint}`);
3984
5733
  if (signal.aborted) return publicError("ABORTED", "Request aborted");
5734
+ if (endpoint === "authorization" || endpoint === "beginLogin" || endpoint === "cancelLogin" || endpoint === "selectAuthorization") {
5735
+ const data = payload;
5736
+ const region = data?.region;
5737
+ if (region !== "global" && region !== "china" || region !== resolveConfig().region) return publicError("INTERNAL", "The active Qoder service region is required");
5738
+ return executeRpc("manage Qoder authorization", async () => {
5739
+ if (endpoint === "beginLogin") return authorization.begin(region);
5740
+ if (endpoint === "cancelLogin") {
5741
+ await authorization.cancel(region);
5742
+ return authorization.describe(region);
5743
+ }
5744
+ if (endpoint === "selectAuthorization") {
5745
+ const method = data?.method;
5746
+ if (method !== "pat" && method !== "browser" && method !== "none") throw new QoderLlmError("Invalid authorization method.", "INVALID_REQUEST");
5747
+ return authorization.select(region, method);
5748
+ }
5749
+ return authorization.describe(region);
5750
+ }, signal, logger);
5751
+ }
5752
+ if (endpoint === "queue") {
5753
+ const sessionId = payload?.sessionId;
5754
+ if (typeof sessionId !== "string" || !sessionId.trim() || sessionId.length > 256) return publicError("INTERNAL", "A valid sessionId is required");
5755
+ return {
5756
+ ok: true,
5757
+ value: queueStatus.read(sessionId)
5758
+ };
5759
+ }
3985
5760
  if (endpoint === "models") return await executeRpc("discover Qoder models", () => discoverModels(signal), signal, logger);
3986
5761
  if (endpoint === "sessionTier" || endpoint === "readSessionTier") {
3987
5762
  const data = payload;
3988
5763
  if (typeof data?.sessionId === "string" && typeof data?.modelId === "string" && (endpoint === "readSessionTier" || typeof data?.tierKey === "string") && (data.region === "global" || data.region === "china")) {
3989
5764
  const { sessionId, modelId, tierKey, region } = data;
3990
5765
  return await executeRpc(endpoint === "sessionTier" ? "select session context tier" : "read session context tier", async () => {
5766
+ if (!sessionId.trim() || region !== resolveConfig().region) throw new QoderLlmError("Invalid session context tier scope.", "INVALID_REQUEST");
5767
+ const sessions = ctx.get("sessions");
5768
+ if (sessions && !sessions.get(sessionId)) {
5769
+ const controller = ctx.get("sessionController");
5770
+ if (controller) {
5771
+ const result = await controller.resolveAgent(sessionId);
5772
+ if ("error" in result) throw result.error;
5773
+ }
5774
+ }
5775
+ signal.throwIfAborted();
3991
5776
  if (endpoint === "sessionTier") adapter.setSessionTier(sessionId, modelId, tierKey, region);
3992
5777
  return adapter.readSessionTier({
3993
5778
  sessionId,