dsh-agy 0.2.3 → 0.2.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,136 @@
1
+ import { t as __exportAll } from "./rolldown-runtime-D7D4PA-g.mjs";
2
+ import { D as getAgyBootstrapUserAgent, b as AGY_ENDPOINT_FALLBACKS } from "./accounts-DbTTxX_z.mjs";
3
+ import { i as proxiedFetch } from "./proxy-DQPIwUov.mjs";
4
+ import { i as isLevelThinkingModel, n as catalogModel, r as isChatCallableModelId, t as AGY_PUBLIC_MODELS } from "./catalog-iuikPZIb.mjs";
5
+ import { ReasoningEffortId } from "@deepseek-ai/dsh-llm";
6
+ //#region src/adapter/models.ts
7
+ /**
8
+ * Model discovery: dynamic `v1internal:fetchAvailableModels` as the primary
9
+ * source (fresh ids + per-model quotaInfo), the pinned catalog merged in for
10
+ * capability metadata, and catalog fallback when the endpoint is unreachable.
11
+ */
12
+ var models_exports = /* @__PURE__ */ __exportAll({
13
+ AGY_PROVIDER: () => "agy",
14
+ catalogModelList: () => catalogModelList,
15
+ fetchAvailableModels: () => fetchAvailableModels,
16
+ listAgyModels: () => listAgyModels,
17
+ mergeModelCatalog: () => mergeModelCatalog,
18
+ resolveAgyModel: () => resolveAgyModel
19
+ });
20
+ /** Level-thinking: single id + selectable low/medium/high via thinkingLevel. Default is UI hint, not wire default. */
21
+ const LEVEL_REASONING = Object.freeze({
22
+ efforts: Object.freeze([
23
+ {
24
+ id: ReasoningEffortId("low"),
25
+ name: "Low"
26
+ },
27
+ {
28
+ id: ReasoningEffortId("medium"),
29
+ name: "Medium"
30
+ },
31
+ {
32
+ id: ReasoningEffortId("high"),
33
+ name: "High"
34
+ }
35
+ ]),
36
+ defaultEffort: ReasoningEffortId("medium")
37
+ });
38
+ /**
39
+ * Input modalities per model. Image support follows the catalog's own
40
+ * `supportsVision` metadata for known models (gpt-oss-120b-medium is text-only
41
+ * there); unknown dynamic ids default to vision-capable — the upstream schema
42
+ * accepts inlineData across the board, and a wrong guess surfaces as a clear
43
+ * upstream 400 instead of a silent drop.
44
+ */
45
+ const AGY_INPUT_MODALITIES = ["text", "image"];
46
+ const AGY_TEXT_ONLY_MODALITIES = ["text"];
47
+ function inputModalitiesFor(meta) {
48
+ return [...(meta ? meta.supportsVision === true : true) ? AGY_INPUT_MODALITIES : AGY_TEXT_ONLY_MODALITIES];
49
+ }
50
+ /** Fetch the account's available models from the first reachable endpoint. */
51
+ async function fetchAvailableModels(accessToken, projectId, fetchImpl = proxiedFetch) {
52
+ let lastError = null;
53
+ const body = projectId ? { project: projectId } : {};
54
+ for (const baseEndpoint of AGY_ENDPOINT_FALLBACKS) try {
55
+ const response = await fetchImpl(`${baseEndpoint}/v1internal:fetchAvailableModels`, {
56
+ method: "POST",
57
+ headers: {
58
+ Authorization: `Bearer ${accessToken}`,
59
+ "Content-Type": "application/json",
60
+ "User-Agent": getAgyBootstrapUserAgent()
61
+ },
62
+ body: JSON.stringify(body)
63
+ });
64
+ if (response.ok) return await response.json();
65
+ lastError = /* @__PURE__ */ new Error(`fetchAvailableModels ${response.status} at ${baseEndpoint}`);
66
+ } catch (error) {
67
+ lastError = error;
68
+ }
69
+ throw lastError instanceof Error ? lastError : /* @__PURE__ */ new Error("fetchAvailableModels: all endpoints failed");
70
+ }
71
+ /** Merge dynamic ids with catalog metadata; non-chat models and unknowns keep minimal info. */
72
+ function mergeModelCatalog(dynamic) {
73
+ const entries = [];
74
+ for (const [id, entry] of Object.entries(dynamic.models ?? {})) {
75
+ if (!isChatCallableModelId(id)) continue;
76
+ const meta = catalogModel(id);
77
+ entries.push({
78
+ provider: "agy",
79
+ id,
80
+ name: entry.displayName ?? meta?.name ?? entry.modelName ?? id,
81
+ inputModalities: inputModalitiesFor(meta),
82
+ ...meta ? { context: { contextWindow: meta.contextLength } } : {}
83
+ });
84
+ }
85
+ return entries;
86
+ }
87
+ /** Catalog-only model list used when the endpoint is unreachable. */
88
+ function catalogModelList() {
89
+ return AGY_PUBLIC_MODELS.map((model) => ({
90
+ provider: "agy",
91
+ id: model.id,
92
+ name: model.name,
93
+ inputModalities: inputModalitiesFor(model),
94
+ context: { contextWindow: model.contextLength }
95
+ }));
96
+ }
97
+ /** Adapter-facing listing: dynamic first, catalog fallback. */
98
+ async function listAgyModels(accessToken, projectId, fetchImpl = proxiedFetch) {
99
+ if (!accessToken) return catalogModelList();
100
+ try {
101
+ const merged = mergeModelCatalog(await fetchAvailableModels(accessToken, projectId, fetchImpl));
102
+ return merged.length > 0 ? merged : catalogModelList();
103
+ } catch {
104
+ return catalogModelList();
105
+ }
106
+ }
107
+ /** Resolve one exact model's metadata (catalog-backed; dynamic ids pass through). */
108
+ function resolveAgyModel(provider, model) {
109
+ const meta = catalogModel(model);
110
+ if (isLevelThinkingModel(model)) return {
111
+ provider,
112
+ id: model,
113
+ name: meta?.name ?? model,
114
+ inputModalities: inputModalitiesFor(meta),
115
+ context: { contextWindow: meta?.contextLength ?? 1048576 },
116
+ defaultMaxTokens: meta?.maxOutputTokens ?? 65536,
117
+ reasoning: {
118
+ ...LEVEL_REASONING,
119
+ efforts: [...LEVEL_REASONING.efforts]
120
+ }
121
+ };
122
+ return {
123
+ provider,
124
+ id: model,
125
+ name: meta?.name ?? model,
126
+ inputModalities: inputModalitiesFor(meta),
127
+ ...meta ? {
128
+ context: { contextWindow: meta.contextLength },
129
+ defaultMaxTokens: meta.maxOutputTokens
130
+ } : {}
131
+ };
132
+ }
133
+ //#endregion
134
+ export { resolveAgyModel as i, listAgyModels as n, models_exports as r, catalogModelList as t };
135
+
136
+ //# sourceMappingURL=models-DRWVi33p.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"models-DRWVi33p.mjs","names":[],"sources":["../src/adapter/models.ts"],"sourcesContent":["/**\n * Model discovery: dynamic `v1internal:fetchAvailableModels` as the primary\n * source (fresh ids + per-model quotaInfo), the pinned catalog merged in for\n * capability metadata, and catalog fallback when the endpoint is unreachable.\n */\n\nimport { ReasoningEffortId, type LlmModelInfo, type LlmModelReasoningInfo, type LlmResolvedModelInfo, type ModelModality } from '@deepseek-ai/dsh-llm'\nimport { AGY_ENDPOINT_FALLBACKS, getAgyBootstrapUserAgent } from '../oauth/constants.ts'\nimport { proxiedFetch } from '../proxy.ts'\nimport { AGY_PUBLIC_MODELS, catalogModel, isChatCallableModelId, isLevelThinkingModel } from './catalog.ts'\n\nexport const AGY_PROVIDER = 'agy'\n\n/** Level-thinking: single id + selectable low/medium/high via thinkingLevel. Default is UI hint, not wire default. */\nconst LEVEL_REASONING: LlmModelReasoningInfo = Object.freeze({\n efforts: Object.freeze([\n { id: ReasoningEffortId('low'), name: 'Low' },\n { id: ReasoningEffortId('medium'), name: 'Medium' },\n { id: ReasoningEffortId('high'), name: 'High' },\n ] as const),\n defaultEffort: ReasoningEffortId('medium'),\n} as const)\n\n/**\n * Input modalities per model. Image support follows the catalog's own\n * `supportsVision` metadata for known models (gpt-oss-120b-medium is text-only\n * there); unknown dynamic ids default to vision-capable — the upstream schema\n * accepts inlineData across the board, and a wrong guess surfaces as a clear\n * upstream 400 instead of a silent drop.\n */\nconst AGY_INPUT_MODALITIES = ['text', 'image'] as const\nconst AGY_TEXT_ONLY_MODALITIES = ['text'] as const\n\nfunction inputModalitiesFor(meta: { supportsVision?: boolean } | undefined): ModelModality[] {\n return [...(meta ? meta.supportsVision === true : true) ? AGY_INPUT_MODALITIES : AGY_TEXT_ONLY_MODALITIES]\n}\n\nexport interface DiscoveredModelEntry {\n quotaInfo?: {\n remainingFraction?: number\n resetTime?: string\n }\n displayName?: string\n modelName?: string\n}\n\nexport interface DiscoveredModels {\n models?: Record<string, DiscoveredModelEntry>\n}\n\n/** Fetch the account's available models from the first reachable endpoint. */\nexport async function fetchAvailableModels(\n accessToken: string,\n projectId?: string,\n fetchImpl: typeof fetch = proxiedFetch,\n): Promise<DiscoveredModels> {\n let lastError: unknown = null\n const body = projectId ? { project: projectId } : {}\n for (const baseEndpoint of AGY_ENDPOINT_FALLBACKS) {\n try {\n const response = await fetchImpl(`${baseEndpoint}/v1internal:fetchAvailableModels`, {\n method: 'POST',\n headers: {\n Authorization: `Bearer ${accessToken}`,\n 'Content-Type': 'application/json',\n 'User-Agent': getAgyBootstrapUserAgent(),\n },\n body: JSON.stringify(body),\n })\n if (response.ok) {\n return (await response.json()) as DiscoveredModels\n }\n lastError = new Error(`fetchAvailableModels ${response.status} at ${baseEndpoint}`)\n } catch (error) {\n lastError = error\n }\n }\n throw lastError instanceof Error ? lastError : new Error('fetchAvailableModels: all endpoints failed')\n}\n\n/** Merge dynamic ids with catalog metadata; non-chat models and unknowns keep minimal info. */\nexport function mergeModelCatalog(dynamic: DiscoveredModels): LlmModelInfo[] {\n const entries: LlmModelInfo[] = []\n for (const [id, entry] of Object.entries(dynamic.models ?? {})) {\n if (!isChatCallableModelId(id)) continue\n const meta = catalogModel(id)\n entries.push({\n provider: AGY_PROVIDER,\n id,\n name: entry.displayName ?? meta?.name ?? entry.modelName ?? id,\n inputModalities: inputModalitiesFor(meta),\n ...(meta ? { context: { contextWindow: meta.contextLength } } : {}),\n })\n }\n return entries\n}\n\n/** Catalog-only model list used when the endpoint is unreachable. */\nexport function catalogModelList(): LlmModelInfo[] {\n return AGY_PUBLIC_MODELS.map((model) => ({\n provider: AGY_PROVIDER,\n id: model.id,\n name: model.name,\n inputModalities: inputModalitiesFor(model),\n context: { contextWindow: model.contextLength },\n }))\n}\n\n/** Adapter-facing listing: dynamic first, catalog fallback. */\nexport async function listAgyModels(\n accessToken: string | undefined,\n projectId: string | undefined,\n fetchImpl: typeof fetch = proxiedFetch,\n): Promise<readonly LlmModelInfo[]> {\n if (!accessToken) return catalogModelList()\n try {\n const dynamic = await fetchAvailableModels(accessToken, projectId, fetchImpl)\n const merged = mergeModelCatalog(dynamic)\n return merged.length > 0 ? merged : catalogModelList()\n } catch {\n return catalogModelList()\n }\n}\n\n/** Resolve one exact model's metadata (catalog-backed; dynamic ids pass through). */\nexport function resolveAgyModel(provider: string, model: string): LlmResolvedModelInfo {\n const meta = catalogModel(model)\n if (isLevelThinkingModel(model)) {\n return {\n provider,\n id: model,\n name: meta?.name ?? model,\n inputModalities: inputModalitiesFor(meta),\n context: { contextWindow: meta?.contextLength ?? 1048576 },\n defaultMaxTokens: meta?.maxOutputTokens ?? 65536,\n // Return a shallow copy so callers cannot mutate the frozen singleton.\n reasoning: { ...LEVEL_REASONING, efforts: [...LEVEL_REASONING.efforts] },\n }\n }\n return {\n provider,\n id: model,\n name: meta?.name ?? model,\n inputModalities: inputModalitiesFor(meta),\n ...(meta ? { context: { contextWindow: meta.contextLength }, defaultMaxTokens: meta.maxOutputTokens } : {}),\n }\n}\n"],"mappings":"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"}
@@ -1,7 +1,8 @@
1
- import { A as AgyPoolBlockedError, _ as generateAntigravityRequestId, a as loadMasterKey, c as resolveMasterKeyCodec, g as deriveAntigravitySessionId, i as deriveKey, k as AgyAuthError, l as AgySessionManager, m as resolveAntigravityVersion, n as MASTER_KEY_REF, o as persistMasterKey, r as createAesGcmCodec, s as resolveDshHome, t as JsonAccountStore, w as fetchAgyFirstOk } from "./accounts-BvX5zmPa.mjs";
2
- import { r as setThoughtSignature, t as toAgyRequestBody } from "./translate-C1SMdm5J.mjs";
1
+ import { A as AgyPoolBlockedError, T as fetchAgyFirstOk, _ as deriveAntigravitySessionId, a as deriveKey, c as resolveDshHome, h as resolveAntigravityVersion, i as createAesGcmCodec, k as AgyAuthError, l as resolveMasterKeyCodec, o as loadMasterKey, r as MASTER_KEY_REF, s as persistMasterKey, t as JsonAccountStore, u as AgySessionManager, v as generateAntigravityRequestId } from "./accounts-DbTTxX_z.mjs";
2
+ import { n as isProxyUnreachableError } from "./proxy-DQPIwUov.mjs";
3
+ import { r as setThoughtSignature, t as toAgyRequestBody } from "./translate-COjBTH4-.mjs";
3
4
  import { t as parseAgySse } from "./parse-D8bV2yqa.mjs";
4
- import { i as resolveAgyModel, n as listAgyModels, t as catalogModelList } from "./models-I1UAEk9x.mjs";
5
+ import { i as resolveAgyModel, n as listAgyModels, t as catalogModelList } from "./models-DRWVi33p.mjs";
5
6
  import { randomBytes } from "node:crypto";
6
7
  import { LlmAdapter, LlmError, ProviderRequestId, QUOTA_EXCEEDED_CODE, attributionHeaders } from "@deepseek-ai/dsh-llm";
7
8
  //#region src/runtime/classify.ts
@@ -114,6 +115,10 @@ function classifyFetchError(error) {
114
115
  kind: "network-error",
115
116
  message
116
117
  };
118
+ if (isProxyUnreachableError(error)) return {
119
+ kind: "proxy-unreachable",
120
+ message
121
+ };
117
122
  return {
118
123
  kind: "network-error",
119
124
  message
@@ -127,6 +132,15 @@ function classifyFetchError(error) {
127
132
  * parsing, failure classification, and rotation reporting. All wire details
128
133
  * live in translate.ts / parse.ts / models.ts.
129
134
  */
135
+ /** Collect image refs from user-message content only (spec scope: user images; tool-result nesting out of scope). */
136
+ function collectImageRefs(options) {
137
+ const refs = [];
138
+ for (const message of options.messages) {
139
+ if (message.role !== "user") continue;
140
+ for (const block of message.content) if (block.type === "image") refs.push(block.attachment);
141
+ }
142
+ return refs;
143
+ }
130
144
  const UPSTREAM_ERROR_CODE = "UPSTREAM";
131
145
  /** Build the impersonation headers for one request (per-request randomization applied by the shell). */
132
146
  function buildRequestHeaders(session) {
@@ -163,7 +177,31 @@ var AgyAdapter = class extends LlmAdapter {
163
177
  async resolveModel(provider, model) {
164
178
  return resolveAgyModel(provider, model);
165
179
  }
180
+ /**
181
+ * Pre-resolve every image attachment into base64 bytes before translation.
182
+ * Image input hard-fails with UNSUPPORTED_CONTENT (terminal, never retried)
183
+ * when the store is missing or a read fails — silently dropping images and
184
+ * sending text-only is the exact failure mode this path exists to prevent.
185
+ */
186
+ async resolveRequestImages(options) {
187
+ const refs = collectImageRefs(options);
188
+ const images = /* @__PURE__ */ new Map();
189
+ if (refs.length === 0) return images;
190
+ const store = this.options.resolveAttachments?.();
191
+ if (!store) throw new LlmError("agy image input requires the durable attachment service (in-harness plugin only)", "UNSUPPORTED_CONTENT");
192
+ for (const ref of refs) try {
193
+ const stored = await store.readImage(ref);
194
+ images.set(ref.attachmentId, {
195
+ mediaType: stored.ref.mediaType,
196
+ data: Buffer.from(stored.data).toString("base64")
197
+ });
198
+ } catch (cause) {
199
+ throw new LlmError(`agy image attachment "${ref.attachmentId}" could not be loaded: ${cause instanceof Error ? cause.message : String(cause)}`, "UNSUPPORTED_CONTENT", { cause: cause instanceof Error ? cause : void 0 });
200
+ }
201
+ return images;
202
+ }
166
203
  async *stream(options) {
204
+ const images = await this.resolveRequestImages(options);
167
205
  let session;
168
206
  try {
169
207
  session = await this.options.getSession(options.model);
@@ -187,7 +225,8 @@ var AgyAdapter = class extends LlmAdapter {
187
225
  if (!session) throw new LlmError("No agy account configured — run `dsh-agy login` to authenticate.", "NO_CREDENTIAL");
188
226
  const body = toAgyRequestBody(options, {
189
227
  projectId: session.account.projectId,
190
- sessionId: deriveAntigravitySessionId(session.account.email) ?? void 0
228
+ sessionId: deriveAntigravitySessionId(session.account.email) ?? void 0,
229
+ ...images.size > 0 ? { images } : {}
191
230
  });
192
231
  const headers = buildRequestHeaders(session);
193
232
  let response;
@@ -301,11 +340,12 @@ async function createAgyRuntime(ctx) {
301
340
  adapter: new AgyAdapter({
302
341
  getSession: (model) => sessions.getSession(model),
303
342
  reportFailure: (kind, session, info) => sessions.reportFailure(kind, session, info),
304
- markSuccess: (session) => sessions.markSuccess(session)
343
+ markSuccess: (session) => sessions.markSuccess(session),
344
+ resolveAttachments: () => ctx.get("attachments")
305
345
  })
306
346
  };
307
347
  }
308
348
  //#endregion
309
349
  export { createAgyRuntime as t };
310
350
 
311
- //# sourceMappingURL=plugin-common-Bmz4Ife-.mjs.map
351
+ //# sourceMappingURL=plugin-common-D7lc-W6b.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"plugin-common-D7lc-W6b.mjs","names":[],"sources":["../src/runtime/classify.ts","../src/adapter/adapter.ts","../src/plugin-common.ts"],"sourcesContent":["/**\n * Upstream failure classification: HTTP status + headers + body → FailureKind.\n * Conservative by design: only auth-failure may revoke an account; everything\n * else cools, rotates, or retries.\n */\n\nimport type { FailureKind } from '../types.ts'\nimport { isProxyUnreachableError } from '../proxy.ts'\n\nexport interface ClassifiedError {\n kind: FailureKind\n status?: number\n /** Server-provided retry delay (ms), when the response carried it. */\n retryAfterMs?: number\n /** Server-provided absolute reset time, when the response carried it. */\n resetTime?: string\n message?: string\n /** 429 sub-category, present when kind is rate-limit. */\n rateLimitCategory?: RateLimitCategory\n}\n\n/**\n * 429 sub-category (mirrors CLIProxyAPI/OmniRoute's classify429):\n * - soft_rate_limit: transient burst, retry immediately on the same account\n * - rate_limited: per-minute limit, short cooldown then retry (same account\n * when there is no other)\n * - quota_exhausted: daily/plan quota gone, long cooldown (24h)\n * - unknown: exponential backoff\n */\nexport type RateLimitCategory = 'soft_rate_limit' | 'rate_limited' | 'quota_exhausted' | 'unknown'\n\nconst QUOTA_EXHAUSTED_KEYWORDS = [\n 'quota_exhausted',\n 'quota exhausted',\n 'quota reached',\n 'enable overages',\n 'individual quota',\n]\n\n/** Classify a 429 body into the four upstream categories. */\nexport function classifyRateLimit(\n bodyText: string | undefined,\n retryAfterMs: number | undefined,\n): RateLimitCategory {\n const text = (bodyText ?? '').toLowerCase()\n if (QUOTA_EXHAUSTED_KEYWORDS.some((keyword) => text.includes(keyword))) {\n return 'quota_exhausted'\n }\n if (retryAfterMs !== undefined && retryAfterMs < 3000) {\n return 'soft_rate_limit'\n }\n if (retryAfterMs !== undefined) {\n return 'rate_limited'\n }\n // No explicit retry hint: \"quota\" wording → daily quota, otherwise unknown.\n return text.includes('quota') || text.includes('resource_exhausted') ? 'quota_exhausted' : 'unknown'\n}\n\nconst RATE_LIMIT_RESET_FIELDS = ['resetTime', 'reset_time', 'resetAt', 'quotaResetTime'] as const\n\nfunction extractResetTime(bodyText: string | undefined): string | undefined {\n if (!bodyText) return undefined\n try {\n const data = JSON.parse(bodyText) as Record<string, unknown>\n for (const field of RATE_LIMIT_RESET_FIELDS) {\n const value = data[field]\n if (typeof value === 'string' && value) return value\n if (typeof value === 'number' && Number.isFinite(value)) {\n // Unix ms when large, seconds when small (relative to now).\n return value > 1_000_000_000_000 ? new Date(value).toISOString() : new Date(Date.now() + value * 1000).toISOString()\n }\n }\n const quotaInfo = data.quotaInfo as Record<string, unknown> | undefined\n if (quotaInfo && typeof quotaInfo.resetTime === 'string') return quotaInfo.resetTime\n } catch {\n // not JSON — no reset info\n }\n return undefined\n}\n\nfunction parseRetryAfter(header: string | null): number | undefined {\n if (!header) return undefined\n const seconds = Number(header)\n if (Number.isFinite(seconds) && seconds >= 0) return seconds * 1000\n const date = Date.parse(header)\n if (!Number.isNaN(date)) return Math.max(0, date - Date.now())\n return undefined\n}\n\n/** Classify a completed HTTP response (non-2xx). */\nexport function classifyHttpError(\n status: number,\n headers: Headers,\n bodyText?: string,\n): ClassifiedError {\n const retryAfterMs = parseRetryAfter(headers.get('retry-after'))\n const resetTime = extractResetTime(bodyText)\n\n if (status === 429) {\n const category = classifyRateLimit(bodyText, retryAfterMs)\n return {\n kind: 'rate-limit',\n rateLimitCategory: category,\n status,\n retryAfterMs,\n resetTime,\n message: bodyText ? bodyText.slice(0, 200) : undefined,\n }\n }\n if (status === 401) {\n return { kind: 'auth-failure', status, message: bodyText ? bodyText.slice(0, 200) : undefined }\n }\n if (status === 403) {\n // Google also reports quota walls as 403 RESOURCE_EXHAUSTED, and the\n // endpoint fallback chain ends on hosts answering 403 for \"no license\"\n // (not bad auth). Only treat a 403 as auth-failure when the body carries\n // no quota wording — a false revoke permanently disables the account.\n const category = classifyRateLimit(bodyText, undefined)\n if (category === 'quota_exhausted') {\n return {\n kind: 'rate-limit',\n rateLimitCategory: category,\n status,\n message: bodyText ? bodyText.slice(0, 200) : undefined,\n }\n }\n return { kind: 'auth-failure', status, message: bodyText ? bodyText.slice(0, 200) : undefined }\n }\n if (status === 404) {\n // Model/route missing — nothing to rotate for; surface as transient request error.\n return { kind: 'transient', status, message: bodyText ? bodyText.slice(0, 200) : undefined }\n }\n if (status >= 500) {\n return { kind: 'transient', status, retryAfterMs, message: bodyText ? bodyText.slice(0, 200) : undefined }\n }\n if (status === 400) {\n // A 400 is a request-construction error (permanent — retrying sends the\n // same broken payload). Only capacity-style phrases are recoverable\n // (context overflow / model unavailable), mirroring OmniRoute's\n // classifyProviderError: everything else surfaces as a terminal\n // request-error so rotation does not waste attempts.\n const text = (bodyText ?? '').toLowerCase()\n const recoverable = (\n (text.includes('context') && (text.includes('overflow') || text.includes('too long') || text.includes('exceeded')))\n || (text.includes('model') && (text.includes('not found') || text.includes('unavailable') || text.includes('not supported')))\n )\n if (recoverable) {\n return { kind: 'transient', status, message: bodyText ? bodyText.slice(0, 200) : undefined }\n }\n return { kind: 'request-error', status, message: bodyText ? bodyText.slice(0, 200) : undefined }\n }\n // 400-range others (401 handled above): request-level errors, no rotation semantics.\n return { kind: 'transient', status, message: bodyText ? bodyText.slice(0, 200) : undefined }\n}\n\n/** Classify a fetch-level failure (DNS, refused, timeout, abort). */\nexport function classifyFetchError(error: unknown): ClassifiedError {\n const message = error instanceof Error ? error.message : String(error)\n if (error instanceof DOMException && error.name === 'AbortError') {\n return { kind: 'network-error', message }\n }\n if (isProxyUnreachableError(error)) {\n return { kind: 'proxy-unreachable', message }\n }\n return { kind: 'network-error', message }\n}\n\n/** Classify the result of a refresh attempt (shared by adapter + verify paths). */\nexport function classifyRefreshFailure(status: number, code?: string): ClassifiedError {\n if (code === 'invalid_grant') return { kind: 'auth-failure', status, message: code }\n if (status === 429) return { kind: 'rate-limit', status, message: code }\n if (status >= 500) return { kind: 'transient', status, message: code }\n return { kind: 'transient', status, message: code }\n}\n\nexport type { FailureKind }\n","/**\n * AgyAdapter: the DSH seam. A thin orchestrator over the deep modules —\n * account session resolution (shell-provided), request translation, SSE\n * parsing, failure classification, and rotation reporting. All wire details\n * live in translate.ts / parse.ts / models.ts.\n */\n\nimport {\n LlmAdapter,\n LlmError,\n ProviderRequestId,\n QUOTA_EXCEEDED_CODE,\n attributionHeaders,\n} from '@deepseek-ai/dsh-llm'\nimport type {\n GenerateOptions,\n LlmModelInfo,\n LlmProviderInfo,\n LlmResolvedModelInfo,\n StreamChunk,\n ToolSchema,\n} from '@deepseek-ai/dsh-llm'\nimport { AgyAuthError, AgyPoolBlockedError } from '../types.ts'\nimport type { AgyAccountSession, FailureKind, ManagedAccount, OAuthAuthDetails } from '../types.ts'\nimport type { RateLimitCategory } from '../runtime/classify.ts'\nimport { fetchAgyFirstOk } from '../oauth/constants.ts'\nimport { classifyFetchError, classifyHttpError } from '../runtime/classify.ts'\nimport { deriveAntigravitySessionId, generateAntigravityRequestId } from '../runtime/identity.ts'\nimport { setThoughtSignature } from '../runtime/signature-cache.ts'\nimport { toAgyRequestBody } from './translate.ts'\nimport type { AgyResolvedImage } from './translate.ts'\nimport { parseAgySse } from './parse.ts'\nimport { AGY_PROVIDER, catalogModelList, listAgyModels, resolveAgyModel } from './models.ts'\n\nexport type { AgyAccountSession }\n\n/**\n * Structural view of the harness attachment service (ctx.attachments).\n * Deliberately not an import of @deepseek-ai/dsh-attachment: the CLI bundle\n * must stay free of harness runtime dependencies, and the real store\n * satisfies this shape.\n */\nexport interface AgyAttachmentStore {\n readImage(ref: {\n attachmentId: string\n mediaType: string\n }): Promise<{ ref: { mediaType: string }; data: Uint8Array }>\n}\n\n/** Collect image refs from user-message content only (spec scope: user images; tool-result nesting out of scope). */\nfunction collectImageRefs(options: GenerateOptions): Array<{ attachmentId: string; mediaType: string }> {\n const refs: Array<{ attachmentId: string; mediaType: string }> = []\n for (const message of options.messages) {\n if (message.role !== 'user') continue\n for (const block of message.content) {\n if (block.type === 'image') refs.push(block.attachment)\n }\n }\n return refs\n}\n\nexport interface AgyAdapterOptions {\n /** Resolve the active account for a request (model-aware: family-scoped quota ranking). */\n getSession(model?: string): Promise<AgyAccountSession | undefined>\n /** Report a classified upstream failure so the shell can cool/rotate/revoke. */\n reportFailure(\n kind: FailureKind,\n session: AgyAccountSession,\n info?: {\n retryAfterMs?: number\n status?: number\n rateLimitCategory?: RateLimitCategory\n /** Server-reported absolute reset time; drives precise cooldowns. */\n resetTime?: string\n /** Requested model id; drives family-scoped rate-limit bookkeeping. */\n model?: string\n },\n ): Promise<void>\n /** Report a clean stream completion (resets the failure counter). */\n markSuccess?(session: AgyAccountSession): Promise<void>\n /** Resolve the harness attachment store; undefined outside the harness (standalone CLI). */\n resolveAttachments?(): AgyAttachmentStore | undefined\n}\n\nconst UPSTREAM_ERROR_CODE = 'UPSTREAM'\n\n/** Build the impersonation headers for one request (per-request randomization applied by the shell). */\nexport function buildRequestHeaders(session: AgyAccountSession): Record<string, string> {\n return {\n authorization: `Bearer ${session.auth.access}`,\n 'content-type': 'application/json',\n accept: 'text/event-stream',\n 'x-goog-request-id': generateAntigravityRequestId(),\n ...attributionHeaders(),\n ...session.impersonation,\n }\n}\n\nexport class AgyAdapter extends LlmAdapter {\n private readonly options: AgyAdapterOptions\n\n constructor(options: AgyAdapterOptions) {\n super()\n this.options = options\n }\n\n override providerInfo(_provider: string): LlmProviderInfo {\n return { id: AGY_PROVIDER, name: 'Antigravity (agy)' }\n }\n\n override async listModels(_provider: string): Promise<readonly LlmModelInfo[]> {\n try {\n const session = await this.options.getSession()\n return await listAgyModels(session?.auth.access, session?.account.projectId)\n } catch (error) {\n if (error instanceof AgyPoolBlockedError || error instanceof AgyAuthError) {\n return catalogModelList()\n }\n throw error\n }\n }\n\n override async resolveModel(provider: string, model: string): Promise<LlmResolvedModelInfo> {\n return resolveAgyModel(provider, model)\n }\n\n /**\n * Pre-resolve every image attachment into base64 bytes before translation.\n * Image input hard-fails with UNSUPPORTED_CONTENT (terminal, never retried)\n * when the store is missing or a read fails — silently dropping images and\n * sending text-only is the exact failure mode this path exists to prevent.\n */\n private async resolveRequestImages(options: GenerateOptions): Promise<Map<string, AgyResolvedImage>> {\n const refs = collectImageRefs(options)\n const images = new Map<string, AgyResolvedImage>()\n if (refs.length === 0) return images\n const store = this.options.resolveAttachments?.()\n if (!store) {\n throw new LlmError(\n 'agy image input requires the durable attachment service (in-harness plugin only)',\n 'UNSUPPORTED_CONTENT',\n )\n }\n for (const ref of refs) {\n try {\n const stored = await store.readImage(ref)\n images.set(ref.attachmentId, {\n mediaType: stored.ref.mediaType,\n data: Buffer.from(stored.data).toString('base64'),\n })\n } catch (cause) {\n throw new LlmError(\n `agy image attachment \"${ref.attachmentId}\" could not be loaded: ${cause instanceof Error ? cause.message : String(cause)}`,\n 'UNSUPPORTED_CONTENT',\n { cause: cause instanceof Error ? cause : undefined },\n )\n }\n }\n return images\n }\n\n override async *stream(options: GenerateOptions): AsyncIterable<StreamChunk> {\n // Spec D1 sequence: resolve images first — a locally-failing image request\n // must surface UNSUPPORTED_CONTENT (user story 8) instead of being masked\n // by account-pool errors, and must not touch pool state at all.\n const images = await this.resolveRequestImages(options)\n let session: AgyAccountSession | undefined\n try {\n session = await this.options.getSession(options.model)\n } catch (error) {\n if (error instanceof AgyAuthError) {\n if (error.kind === 'transport') {\n throw new LlmError(error.message, 'TRANSPORT', { cause: error })\n }\n if (error.kind === 'rate-limit') {\n throw new LlmError(error.message, 'RATE_LIMIT', {\n requestId: ProviderRequestId(generateAntigravityRequestId()),\n })\n }\n throw new LlmError(error.message, 'INVALID_CREDENTIAL', { cause: error })\n }\n if (error instanceof AgyPoolBlockedError) {\n if (error.kind === 'quota-exhausted') {\n throw new LlmError(error.message, QUOTA_EXCEEDED_CODE)\n }\n const delta = Math.ceil(error.blockedUntil - Date.now())\n const providerRetryAfterMs = Number.isFinite(delta) && delta > 0 ? delta : 1\n throw new LlmError(error.message, 'RATE_LIMIT', {\n providerRetryAfterMs,\n requestId: ProviderRequestId(generateAntigravityRequestId()),\n })\n }\n throw error\n }\n if (!session) {\n throw new LlmError(\n 'No agy account configured — run `dsh-agy login` to authenticate.',\n 'NO_CREDENTIAL',\n )\n }\n\n const body = toAgyRequestBody(options, {\n projectId: session.account.projectId,\n sessionId: deriveAntigravitySessionId(session.account.email) ?? undefined,\n ...(images.size > 0 ? { images } : {}),\n })\n const headers = buildRequestHeaders(session)\n\n let response: Response\n try {\n response = await fetchAgyFirstOk('/v1internal:streamGenerateContent?alt=sse', {\n method: 'POST',\n headers,\n body: JSON.stringify(body),\n signal: options.signal,\n })\n } catch (error) {\n const classified = classifyFetchError(error)\n await this.options.reportFailure(classified.kind, session)\n throw new LlmError(classified.message ?? 'agy fetch failed', 'TRANSPORT', { cause: error })\n }\n\n if (!response.ok) {\n const bodyText = await response.text().catch(() => undefined)\n const classified = classifyHttpError(response.status, response.headers, bodyText)\n await this.options.reportFailure(classified.kind, session, {\n retryAfterMs: classified.retryAfterMs,\n status: response.status,\n rateLimitCategory: classified.rateLimitCategory,\n resetTime: classified.resetTime,\n model: options.model,\n })\n if (classified.kind === 'rate-limit') {\n // soft/rate limits are retryable by the harness (RATE_LIMIT + delay);\n // daily quota exhaustion is terminal (QUOTA, 24h cooldown already set).\n if (classified.rateLimitCategory === 'quota_exhausted') {\n throw new LlmError(\n `agy daily quota exhausted (${response.status}): ${classified.message ?? ''}`,\n QUOTA_EXCEEDED_CODE,\n )\n }\n throw new LlmError(\n `agy rate-limited (${response.status}): ${classified.message ?? ''}`,\n 'RATE_LIMIT',\n {\n providerRetryAfterMs: classified.retryAfterMs ?? undefined,\n requestId: ProviderRequestId(generateAntigravityRequestId()),\n },\n )\n }\n if (classified.kind === 'auth-failure') {\n throw new LlmError(\n `agy authentication failed (${response.status}) — run \\`dsh-agy login\\``,\n 'INVALID_CREDENTIAL',\n )\n }\n throw new LlmError(\n `agy upstream error (${response.status}): ${classified.message ?? ''}`,\n UPSTREAM_ERROR_CODE,\n )\n }\n\n if (!response.body) {\n throw new LlmError('agy stream returned no body', UPSTREAM_ERROR_CODE)\n }\n\n try {\n yield* parseAgySse(response.body, {\n signal: options.signal,\n onToolSignature: (toolCallId, signature) => {\n setThoughtSignature(toolCallId, signature)\n },\n })\n await this.options.markSuccess?.(session)\n } catch (error) {\n if (error instanceof DOMException && error.name === 'AbortError') {\n throw new LlmError('agy stream aborted', 'ABORTED', { cause: error })\n }\n await this.options.reportFailure('network-error', session)\n throw new LlmError(\n error instanceof Error ? error.message : 'agy stream parse failed',\n UPSTREAM_ERROR_CODE,\n { cause: error },\n )\n }\n }\n}\n\nexport type { ToolSchema }\n","/**\n * Shared runtime construction for the in-harness plugin entries: master-key\n * codec resolution (credentials seam first, credentials document fallback),\n * account store, session manager, and adapter. Used by the main plugin\n * (adapter registration) and the web plugin (route registration) so both\n * entries operate on the same store.\n */\n\nimport { randomBytes } from 'node:crypto'\nimport type { Context } from '@deepseek-ai/cordis'\nimport { AgyAdapter } from './adapter/adapter.ts'\nimport type { AgyAttachmentStore } from './adapter/adapter.ts'\nimport { AGY_PROVIDER } from './adapter/models.ts'\nimport { resolveAntigravityVersion } from './runtime/version.ts'\nimport { AgySessionManager } from './session.ts'\nimport { JsonAccountStore } from './store/accounts.ts'\nimport type { AccountStore } from './store/accounts.ts'\nimport {\n MASTER_KEY_REF,\n createAesGcmCodec,\n deriveKey,\n loadMasterKey,\n persistMasterKey,\n resolveDshHome,\n resolveMasterKeyCodec,\n} from './store/keyring.ts'\nimport type { SecretCodec } from './store/keyring.ts'\n\nexport interface CredentialsSeam {\n resolve(ref: string): Promise<{ value: string } | undefined>\n set(ref: string, value: string): Promise<void>\n}\n\nfunction codecFrom(masterKey: string): SecretCodec {\n return createAesGcmCodec(deriveKey(masterKey))\n}\n\n/** Resolve or create the master key, preferring the credentials seam when available. */\nexport async function resolveCodec(ctx: Context): Promise<{ codec: SecretCodec; created: boolean }> {\n const dshHome = resolveDshHome()\n const credentials = ctx.get('credentials') as CredentialsSeam | undefined\n\n if (credentials) {\n const resolved = await credentials.resolve(MASTER_KEY_REF)\n if (resolved) return { codec: codecFrom(resolved.value), created: false }\n const fileKey = loadMasterKey(dshHome)\n if (fileKey) return { codec: codecFrom(fileKey), created: false }\n const fresh = randomBytes(32).toString('hex')\n try {\n await credentials.set(MASTER_KEY_REF, fresh)\n return { codec: codecFrom(fresh), created: true }\n } catch {\n // Read-only shadowing: persist to the credentials document directly.\n persistMasterKey(dshHome, fresh)\n return { codec: codecFrom(fresh), created: true }\n }\n }\n\n return resolveMasterKeyCodec(dshHome)\n}\n\n/** Build the store, session manager, and adapter for one plugin entry. */\nexport async function createAgyRuntime(ctx: Context): Promise<{\n store: AccountStore\n sessions: AgySessionManager\n adapter: AgyAdapter\n}> {\n // Warm the version cache (non-blocking) so fingerprint generation inside\n // the rate-limit path never waits on a cold feed.\n void resolveAntigravityVersion().catch(() => {})\n const { codec } = await resolveCodec(ctx)\n const dshHome = resolveDshHome()\n const store = new JsonAccountStore({ file: `${dshHome}/agy-accounts.json`, codec })\n const sessions = new AgySessionManager({ store })\n // Optional background health probe (DSH_AGY_HEALTH_INTERVAL_MS), off by default.\n const healthIntervalMs = Number(process.env.DSH_AGY_HEALTH_INTERVAL_MS ?? 0)\n if (Number.isFinite(healthIntervalMs) && healthIntervalMs > 0) {\n sessions.startHealthProbe(healthIntervalMs)\n }\n const adapter = new AgyAdapter({\n getSession: (model) => sessions.getSession(model),\n reportFailure: (kind, session, info) => sessions.reportFailure(kind, session, info),\n markSuccess: (session) => sessions.markSuccess(session),\n resolveAttachments: () => ctx.get('attachments') as AgyAttachmentStore | undefined,\n })\n return { store, sessions, adapter }\n}\n\nexport { AGY_PROVIDER }\n"],"mappings":"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"}
@@ -0,0 +1,269 @@
1
+ import { t as __exportAll } from "./rolldown-runtime-D7D4PA-g.mjs";
2
+ import { EnvHttpProxyAgent, ProxyAgent } from "undici";
3
+ import { createConnection } from "node:net";
4
+ //#region src/proxy.ts
5
+ /**
6
+ * Proxy-aware fetch: per-account proxy (http/https/socks5) + env fallback.
7
+ * - account.proxy present => per-account dispatcher (fail-closed, not affected by NO_PROXY)
8
+ * - otherwise => EnvHttpProxyAgent (honours HTTP_PROXY/HTTPS_PROXY/NO_PROXY)
9
+ * Applied per-request via dispatcher option so the host's global dispatcher stays untouched.
10
+ */
11
+ var proxy_exports = /* @__PURE__ */ __exportAll({
12
+ _envAgentForTest: () => envAgent,
13
+ dispatcherForAsync: () => dispatcherForAsync,
14
+ isProxyReachable: () => isProxyReachable,
15
+ isProxyUnreachableError: () => isProxyUnreachableError,
16
+ normalizeProxyUrl: () => normalizeProxyUrl,
17
+ proxiedFetch: () => proxiedFetch,
18
+ proxyUrlForLogs: () => proxyUrlForLogs,
19
+ tagProxyUnreachable: () => tagProxyUnreachable
20
+ });
21
+ const envAgent = new EnvHttpProxyAgent();
22
+ const dispatcherCache = /* @__PURE__ */ new Map();
23
+ const SUPPORTED = /* @__PURE__ */ new Set([
24
+ "http:",
25
+ "https:",
26
+ "socks5:",
27
+ "socks5h:"
28
+ ]);
29
+ function defaultPort(protocol) {
30
+ if (protocol === "https:") return "443";
31
+ if (protocol === "socks5:" || protocol === "socks5h:") return "1080";
32
+ return "8080";
33
+ }
34
+ function normalizeProxyUrl(proxyUrl) {
35
+ let raw = proxyUrl.trim();
36
+ if (!raw) throw new Error("[proxy] empty proxy URL");
37
+ if (raw.toLowerCase().startsWith("socks://")) raw = "socks5://" + raw.slice(8);
38
+ const familyMatch = raw.match(/\?family=(ipv4|ipv6)$/);
39
+ const familySuffix = familyMatch ? familyMatch[0] : "";
40
+ const baseRaw = familySuffix ? raw.slice(0, -familySuffix.length) : raw;
41
+ let parsed;
42
+ try {
43
+ parsed = new URL(baseRaw);
44
+ } catch {
45
+ throw new Error(`[proxy] invalid proxy URL: ${proxyUrlForLogs(proxyUrl)}`);
46
+ }
47
+ let protocol = parsed.protocol.toLowerCase();
48
+ if (protocol === "socks5h:") protocol = "socks5:";
49
+ if (!SUPPORTED.has(protocol) && protocol !== "socks5:") {
50
+ if (!SUPPORTED.has(parsed.protocol.toLowerCase())) throw new Error(`[proxy] unsupported protocol: ${parsed.protocol}`);
51
+ }
52
+ if (![
53
+ "http:",
54
+ "https:",
55
+ "socks5:"
56
+ ].includes(protocol)) throw new Error(`[proxy] unsupported protocol: ${parsed.protocol}`);
57
+ if (!parsed.hostname) throw new Error("[proxy] missing host");
58
+ let port = parsed.port;
59
+ if (!port) port = defaultPort(protocol);
60
+ const portNum = Number(port);
61
+ if (!Number.isInteger(portNum) || portNum < 1 || portNum > 65535) throw new Error("[proxy] invalid port");
62
+ const auth = parsed.username ? `${encodeURIComponent(parsed.username)}${parsed.password ? `:${encodeURIComponent(parsed.password)}` : ""}@` : "";
63
+ const normalizedBase = `${protocol}//${auth}${parsed.hostname}:${port}`;
64
+ return familySuffix ? `${normalizedBase}${familySuffix}` : normalizedBase;
65
+ }
66
+ function proxyUrlForLogs(proxyUrl) {
67
+ try {
68
+ const fam = proxyUrl.match(/\?family=(ipv4|ipv6)$/);
69
+ const base = fam ? proxyUrl.slice(0, -fam[0].length) : proxyUrl;
70
+ const u = new URL(base);
71
+ const port = u.port || defaultPort(u.protocol);
72
+ return `${u.protocol}//${u.hostname}:${port}`;
73
+ } catch {
74
+ return proxyUrl;
75
+ }
76
+ }
77
+ const PROXY_UNREACHABLE_CODES = /* @__PURE__ */ new Set([
78
+ "ECONNREFUSED",
79
+ "ECONNRESET",
80
+ "ETIMEDOUT",
81
+ "ENETUNREACH",
82
+ "EHOSTUNREACH",
83
+ "EPIPE",
84
+ "UND_ERR_CONNECT_TIMEOUT",
85
+ "UND_ERR_SOCKET"
86
+ ]);
87
+ function isLoopbackUrl(url) {
88
+ try {
89
+ const h = (typeof url === "string" ? new URL(url) : url).hostname.toLowerCase();
90
+ return h === "localhost" || h === "127.0.0.1" || h === "::1" || h === "::ffff:127.0.0.1";
91
+ } catch {
92
+ return false;
93
+ }
94
+ }
95
+ function isProxyUnreachableError(err) {
96
+ const seen = /* @__PURE__ */ new Set();
97
+ let cur = err;
98
+ for (let depth = 0; cur && depth < 5 && !seen.has(cur); depth++) {
99
+ seen.add(cur);
100
+ if (cur && typeof cur === "object") {
101
+ const code = cur.code;
102
+ if (typeof code === "string" && PROXY_UNREACHABLE_CODES.has(code)) return true;
103
+ const errorCode = cur.errorCode;
104
+ if (errorCode === "proxy_unreachable" || errorCode === "PROXY_UNREACHABLE") return true;
105
+ if (cur.statusCode === 503) {
106
+ const m = cur.message;
107
+ if (typeof m === "string" && m.includes("Proxy unreachable")) return true;
108
+ }
109
+ const msg = cur.message;
110
+ if (typeof msg === "string") {
111
+ if (msg.includes("proxy_unreachable") || msg.includes("PROXY_UNREACHABLE")) return true;
112
+ for (const c of PROXY_UNREACHABLE_CODES) if (msg.includes(c)) return true;
113
+ }
114
+ }
115
+ cur = cur?.cause;
116
+ if (!cur && err?.errors && depth === 0) {
117
+ const errs = err.errors;
118
+ if (Array.isArray(errs)) {
119
+ for (const e of errs) if (isProxyUnreachableError(e)) return true;
120
+ }
121
+ }
122
+ }
123
+ return false;
124
+ }
125
+ function tagProxyUnreachable(err) {
126
+ if (isProxyUnreachableError(err)) {
127
+ const e = err;
128
+ e.code = "PROXY_UNREACHABLE";
129
+ e.errorCode = "proxy_unreachable";
130
+ }
131
+ return err;
132
+ }
133
+ const FAST_FAIL_TIMEOUT_MS = 2e3;
134
+ const HEALTHY_TTL_MS = 3e4;
135
+ const UNHEALTHY_TTL_MS = 2e3;
136
+ const healthCache = /* @__PURE__ */ new Map();
137
+ const healthInflight = /* @__PURE__ */ new Map();
138
+ function tcpCheck(host, port, timeoutMs) {
139
+ return new Promise((resolve) => {
140
+ const socket = createConnection({
141
+ host,
142
+ port
143
+ }, () => {
144
+ socket.destroy();
145
+ resolve(true);
146
+ });
147
+ socket.setTimeout(timeoutMs);
148
+ socket.on("error", () => resolve(false));
149
+ socket.on("timeout", () => {
150
+ socket.destroy();
151
+ resolve(false);
152
+ });
153
+ });
154
+ }
155
+ async function isProxyReachable(proxyUrl, timeoutMs = FAST_FAIL_TIMEOUT_MS) {
156
+ const key = normalizeProxyUrl(proxyUrl);
157
+ const cached = healthCache.get(key);
158
+ if (cached && Date.now() - cached.at < cached.ttl) return cached.healthy;
159
+ const existing = healthInflight.get(key);
160
+ if (existing) return existing;
161
+ let url;
162
+ try {
163
+ const base = key.replace(/\?family=(ipv4|ipv6)$/, "");
164
+ url = new URL(base);
165
+ } catch {
166
+ healthCache.set(key, {
167
+ healthy: false,
168
+ at: Date.now(),
169
+ ttl: UNHEALTHY_TTL_MS
170
+ });
171
+ return false;
172
+ }
173
+ const probe = tcpCheck(url.hostname.replace(/^\[/, "").replace(/\]$/, ""), Number(url.port || defaultPort(url.protocol)), timeoutMs).then((healthy) => {
174
+ healthCache.set(key, {
175
+ healthy,
176
+ at: Date.now(),
177
+ ttl: healthy ? HEALTHY_TTL_MS : UNHEALTHY_TTL_MS
178
+ });
179
+ return healthy;
180
+ });
181
+ healthInflight.set(key, probe);
182
+ try {
183
+ return await probe;
184
+ } finally {
185
+ if (healthInflight.get(key) === probe) healthInflight.delete(key);
186
+ }
187
+ }
188
+ async function createSocksDispatcher(proxyUrl, dispatcherOpts) {
189
+ const { SocksProxyAgent } = await import("socks-proxy-agent");
190
+ return new SocksProxyAgent(proxyUrl);
191
+ }
192
+ function createHttpDispatcher(proxyUrl, dispatcherOpts) {
193
+ const clean = proxyUrl.replace(/\?family=(ipv4|ipv6)$/, "");
194
+ return new ProxyAgent({
195
+ uri: clean,
196
+ proxyTunnel: true,
197
+ ...dispatcherOpts
198
+ });
199
+ }
200
+ const DISPATCHER_OPTS = {
201
+ headersTimeout: 3e4,
202
+ bodyTimeout: 3e4,
203
+ connectTimeout: 1e4,
204
+ keepAliveTimeout: 1,
205
+ keepAliveMaxTimeout: 1,
206
+ pipelining: 0
207
+ };
208
+ async function dispatcherForAsync(proxyUrl) {
209
+ if (!proxyUrl) return envAgent;
210
+ const normalized = normalizeProxyUrl(proxyUrl);
211
+ const cached = dispatcherCache.get(normalized);
212
+ if (cached) return cached;
213
+ const famClean = normalized.replace(/\?family=(ipv4|ipv6)$/, "");
214
+ let dispatcher;
215
+ if (famClean.startsWith("socks5:")) dispatcher = await createSocksDispatcher(normalized, DISPATCHER_OPTS);
216
+ else dispatcher = createHttpDispatcher(normalized, DISPATCHER_OPTS);
217
+ dispatcherCache.set(normalized, dispatcher);
218
+ return dispatcher;
219
+ }
220
+ function getTargetUrlString(input) {
221
+ if (typeof input === "string") return input;
222
+ try {
223
+ if (input instanceof URL) return input.toString();
224
+ if (typeof input.url === "string") return input.url;
225
+ } catch {}
226
+ return "";
227
+ }
228
+ /** fetch() that respects per-account proxyUrl (when given) otherwise env. */
229
+ const proxiedFetch = async (input, init, opts) => {
230
+ const proxyUrl = opts?.proxyUrl ?? init?.proxyUrl;
231
+ const cleanInit = { ...init };
232
+ if (cleanInit.proxyUrl) delete cleanInit.proxyUrl;
233
+ if (!proxyUrl) return fetch(input, {
234
+ ...cleanInit,
235
+ dispatcher: envAgent
236
+ });
237
+ const targetStr = getTargetUrlString(input);
238
+ if (targetStr && isLoopbackUrl(targetStr)) return fetch(input, cleanInit);
239
+ try {
240
+ if (!await isProxyReachable(proxyUrl)) {
241
+ const err = /* @__PURE__ */ new Error(`[Proxy Fast-Fail] Proxy unreachable: ${proxyUrlForLogs(proxyUrl)}`);
242
+ err.code = "PROXY_UNREACHABLE";
243
+ err.errorCode = "proxy_unreachable";
244
+ err.statusCode = 503;
245
+ throw err;
246
+ }
247
+ } catch (e) {
248
+ if (e?.errorCode === "proxy_unreachable") throw e;
249
+ if (e instanceof Error && e.message.startsWith("[proxy]")) throw e;
250
+ }
251
+ let dispatcher;
252
+ try {
253
+ dispatcher = await dispatcherForAsync(proxyUrl);
254
+ } catch (e) {
255
+ throw tagProxyUnreachable(e);
256
+ }
257
+ try {
258
+ return await fetch(input, {
259
+ ...cleanInit,
260
+ dispatcher
261
+ });
262
+ } catch (err) {
263
+ throw tagProxyUnreachable(err);
264
+ }
265
+ };
266
+ //#endregion
267
+ export { proxyUrlForLogs as a, proxiedFetch as i, isProxyUnreachableError as n, proxy_exports as o, normalizeProxyUrl as r, isProxyReachable as t };
268
+
269
+ //# sourceMappingURL=proxy-DQPIwUov.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"proxy-DQPIwUov.mjs","names":[],"sources":["../src/proxy.ts"],"sourcesContent":["/**\n * Proxy-aware fetch: per-account proxy (http/https/socks5) + env fallback.\n * - account.proxy present => per-account dispatcher (fail-closed, not affected by NO_PROXY)\n * - otherwise => EnvHttpProxyAgent (honours HTTP_PROXY/HTTPS_PROXY/NO_PROXY)\n * Applied per-request via dispatcher option so the host's global dispatcher stays untouched.\n */\n\nimport { EnvHttpProxyAgent, ProxyAgent } from 'undici'\nimport { createConnection } from 'node:net'\nimport { createRequire } from 'node:module'\n\nconst envAgent = new EnvHttpProxyAgent()\n\n/** The env proxy agent (exported for tests). */\nexport const proxyAgent = envAgent\n\n// ── Dispatcher cache (bounded by MAX_ACCOUNTS=10, no leak concern) ──\nconst dispatcherCache = new Map<string, any>()\n\n// ── Proxy URL normalization ──\nconst SUPPORTED = new Set(['http:', 'https:', 'socks5:', 'socks5h:'])\n\nfunction defaultPort(protocol: string): string {\n if (protocol === 'https:') return '443'\n if (protocol === 'socks5:' || protocol === 'socks5h:') return '1080'\n return '8080'\n}\n\nexport function normalizeProxyUrl(proxyUrl: string): string {\n let raw = proxyUrl.trim()\n if (!raw) throw new Error('[proxy] empty proxy URL')\n // alias socks:// -> socks5://\n if (raw.toLowerCase().startsWith('socks://')) raw = 'socks5://' + raw.slice('socks://'.length)\n // strip family marker before parse, re-append after\n const familyMatch = raw.match(/\\?family=(ipv4|ipv6)$/)\n const familySuffix = familyMatch ? familyMatch[0] : ''\n const baseRaw = familySuffix ? raw.slice(0, -familySuffix.length) : raw\n\n let parsed: URL\n try {\n parsed = new URL(baseRaw)\n } catch {\n throw new Error(`[proxy] invalid proxy URL: ${proxyUrlForLogs(proxyUrl)}`)\n }\n // normalize socks5h -> socks5 (remote DNS, same agent)\n let protocol = parsed.protocol.toLowerCase()\n if (protocol === 'socks5h:') protocol = 'socks5:'\n if (!SUPPORTED.has(protocol) && protocol !== 'socks5:') {\n // allow socks5h already normalized\n if (!SUPPORTED.has(parsed.protocol.toLowerCase())) {\n throw new Error(`[proxy] unsupported protocol: ${parsed.protocol}`)\n }\n }\n // ensure protocol is one of http/https/socks5\n if (!['http:', 'https:', 'socks5:'].includes(protocol)) {\n throw new Error(`[proxy] unsupported protocol: ${parsed.protocol}`)\n }\n if (!parsed.hostname) throw new Error('[proxy] missing host')\n\n let port = parsed.port\n if (!port) port = defaultPort(protocol)\n const portNum = Number(port)\n if (!Number.isInteger(portNum) || portNum < 1 || portNum > 65535) {\n throw new Error('[proxy] invalid port')\n }\n // Build auth with proper encoding (parsed.username/password are decoded)\n const auth = parsed.username\n ? `${encodeURIComponent(parsed.username)}${parsed.password ? `:${encodeURIComponent(parsed.password)}` : ''}@`\n : ''\n const normalizedBase = `${protocol}//${auth}${parsed.hostname}:${port}`\n return familySuffix ? `${normalizedBase}${familySuffix}` : normalizedBase\n}\n\nexport function proxyUrlForLogs(proxyUrl: string): string {\n try {\n // handle family suffix\n const fam = proxyUrl.match(/\\?family=(ipv4|ipv6)$/)\n const base = fam ? proxyUrl.slice(0, -fam[0].length) : proxyUrl\n const u = new URL(base)\n const port = u.port || defaultPort(u.protocol)\n return `${u.protocol}//${u.hostname}:${port}`\n } catch {\n return proxyUrl\n }\n}\n\n// ── Proxy unreachable detection (mirrors OmniRoute proxyFetch.ts:30) ──\nconst PROXY_UNREACHABLE_CODES = new Set([\n 'ECONNREFUSED',\n 'ECONNRESET',\n 'ETIMEDOUT',\n 'ENETUNREACH',\n 'EHOSTUNREACH',\n 'EPIPE',\n 'UND_ERR_CONNECT_TIMEOUT',\n 'UND_ERR_SOCKET',\n])\n\nfunction isLoopbackUrl(url: string | URL): boolean {\n try {\n const u = typeof url === 'string' ? new URL(url as string) : (url as URL)\n const h = u.hostname.toLowerCase()\n return h === 'localhost' || h === '127.0.0.1' || h === '::1' || h === '::ffff:127.0.0.1'\n } catch {\n return false\n }\n}\n\nexport function isProxyUnreachableError(err: unknown): boolean {\n const seen = new Set<unknown>()\n let cur: unknown = err\n for (let depth = 0; cur && depth < 5 && !seen.has(cur); depth++) {\n seen.add(cur)\n if (cur && typeof cur === 'object') {\n const code = (cur as { code?: unknown }).code\n if (typeof code === 'string' && PROXY_UNREACHABLE_CODES.has(code)) return true\n const errorCode = (cur as { errorCode?: unknown }).errorCode\n if (errorCode === 'proxy_unreachable' || errorCode === 'PROXY_UNREACHABLE') return true\n const statusCode = (cur as { statusCode?: unknown }).statusCode\n if (statusCode === 503) {\n const m = (cur as { message?: unknown }).message\n if (typeof m === 'string' && m.includes('Proxy unreachable')) return true\n }\n const msg = (cur as { message?: unknown }).message\n if (typeof msg === 'string') {\n if (msg.includes('proxy_unreachable') || msg.includes('PROXY_UNREACHABLE')) return true\n for (const c of PROXY_UNREACHABLE_CODES) {\n if (msg.includes(c)) return true\n }\n }\n }\n cur = (cur as { cause?: unknown })?.cause\n // Also handle AggregateError.errors (undici Happy Eyeballs)\n if (!cur && (err as { errors?: unknown })?.errors && depth === 0) {\n const errs = (err as { errors?: unknown[] }).errors\n if (Array.isArray(errs)) {\n for (const e of errs) if (isProxyUnreachableError(e)) return true\n }\n }\n }\n return false\n}\n\nexport function tagProxyUnreachable<T>(err: T): T {\n if (isProxyUnreachableError(err)) {\n const e = err as unknown as Error & { code?: string; errorCode?: string }\n e.code = 'PROXY_UNREACHABLE'\n e.errorCode = 'proxy_unreachable'\n }\n return err\n}\n\n// ── Fast-fail TCP reachability (2s, 30s healthy / 2s unhealthy cache) ──\nconst FAST_FAIL_TIMEOUT_MS = 2000\nconst HEALTHY_TTL_MS = 30_000\nconst UNHEALTHY_TTL_MS = 2000\ntype HealthEntry = { healthy: boolean; at: number; ttl: number }\nconst healthCache = new Map<string, HealthEntry>()\nconst healthInflight = new Map<string, Promise<boolean>>()\n\nfunction tcpCheck(host: string, port: number, timeoutMs: number): Promise<boolean> {\n return new Promise((resolve) => {\n const socket = createConnection({ host, port }, () => {\n socket.destroy()\n resolve(true)\n })\n socket.setTimeout(timeoutMs)\n socket.on('error', () => resolve(false))\n socket.on('timeout', () => {\n socket.destroy()\n resolve(false)\n })\n })\n}\n\nexport async function isProxyReachable(proxyUrl: string, timeoutMs = FAST_FAIL_TIMEOUT_MS): Promise<boolean> {\n const key = normalizeProxyUrl(proxyUrl)\n const cached = healthCache.get(key)\n if (cached && Date.now() - cached.at < cached.ttl) return cached.healthy\n const existing = healthInflight.get(key)\n if (existing) return existing\n let url: URL\n try {\n const base = key.replace(/\\?family=(ipv4|ipv6)$/, '')\n url = new URL(base)\n } catch {\n healthCache.set(key, { healthy: false, at: Date.now(), ttl: UNHEALTHY_TTL_MS })\n return false\n }\n const host = url.hostname.replace(/^\\[/, '').replace(/\\]$/, '')\n const port = Number(url.port || defaultPort(url.protocol))\n const probe = tcpCheck(host, port, timeoutMs).then((healthy) => {\n healthCache.set(key, { healthy, at: Date.now(), ttl: healthy ? HEALTHY_TTL_MS : UNHEALTHY_TTL_MS })\n return healthy\n })\n healthInflight.set(key, probe)\n try {\n return await probe\n } finally {\n if (healthInflight.get(key) === probe) healthInflight.delete(key)\n }\n}\n\nexport function _clearProxyHealthCacheForTest(): void {\n healthCache.clear()\n healthInflight.clear()\n}\n\n// ── Dispatcher creation (cached) ──\nasync function createSocksDispatcher(proxyUrl: string, dispatcherOpts: Record<string, unknown>): Promise<any> {\n // @ts-ignore — optional dep, only required for socks5\n const { SocksProxyAgent } = await import('socks-proxy-agent')\n // socks-proxy-agent is compatible with undici dispatcher interface via fetch\n return new (SocksProxyAgent as any)(proxyUrl) as any\n}\n\nfunction createHttpDispatcher(proxyUrl: string, dispatcherOpts: Record<string, unknown>): any {\n const clean = proxyUrl.replace(/\\?family=(ipv4|ipv6)$/, '')\n return new ProxyAgent({\n uri: clean,\n // tunnel all (http+https) via CONNECT, same as OmniRoute\n proxyTunnel: true,\n ...dispatcherOpts,\n } as any)\n}\n\nconst DISPATCHER_OPTS = {\n headersTimeout: 30_000,\n bodyTimeout: 30_000,\n connectTimeout: 10_000,\n keepAliveTimeout: 1,\n keepAliveMaxTimeout: 1,\n pipelining: 0,\n} as const\n\nexport function dispatcherFor(proxyUrl?: string): any | undefined {\n if (!proxyUrl) return envAgent as any\n const normalized = normalizeProxyUrl(proxyUrl)\n const cached = dispatcherCache.get(normalized)\n if (cached) return cached\n const famClean = normalized.replace(/\\?family=(ipv4|ipv6)$/, '')\n let dispatcher: any\n if (famClean.startsWith('socks5:')) {\n // SOCKS needs SocksProxyAgent. Try sync require for cached case; otherwise prefer async path (proxiedFetch uses dispatcherForAsync).\n try {\n const rq = createRequire(import.meta.url)\n const mod = rq('socks-proxy-agent') as { SocksProxyAgent?: new (u: string) => unknown }\n const Cls = mod.SocksProxyAgent\n if (Cls) {\n dispatcher = new (Cls as unknown as new (u: string) => unknown)(normalized) as unknown\n } else {\n throw new Error('no SocksProxyAgent')\n }\n } catch {\n throw new Error('[proxy] socks dispatcher requires async creation — use dispatcherForAsync or proxiedFetch with proxyUrl')\n }\n } else {\n dispatcher = createHttpDispatcher(normalized, DISPATCHER_OPTS as any)\n }\n dispatcherCache.set(normalized, dispatcher)\n return dispatcher\n}\n\nexport async function dispatcherForAsync(proxyUrl?: string): Promise<any | undefined> {\n if (!proxyUrl) return envAgent as any\n const normalized = normalizeProxyUrl(proxyUrl)\n const cached = dispatcherCache.get(normalized)\n if (cached) return cached\n const famClean = normalized.replace(/\\?family=(ipv4|ipv6)$/, '')\n let dispatcher: any\n if (famClean.startsWith('socks5:')) {\n dispatcher = await createSocksDispatcher(normalized, DISPATCHER_OPTS as any)\n } else {\n dispatcher = createHttpDispatcher(normalized, DISPATCHER_OPTS as any)\n }\n dispatcherCache.set(normalized, dispatcher)\n return dispatcher\n}\n\nexport function _clearDispatcherCacheForTest(): void {\n dispatcherCache.clear()\n}\n\nfunction getTargetUrlString(input: Parameters<typeof fetch>[0]): string {\n if (typeof input === 'string') return input\n try {\n if (input instanceof URL) return input.toString()\n if (typeof (input as Request).url === 'string') return (input as Request).url\n } catch {\n // ignore\n }\n return ''\n}\n\n/** fetch() that respects per-account proxyUrl (when given) otherwise env. */\nexport const proxiedFetch = async (\n input: Parameters<typeof fetch>[0],\n init?: Parameters<typeof fetch>[1] & { proxyUrl?: string },\n opts?: { proxyUrl?: string },\n): Promise<Response> => {\n const proxyUrl = (opts as any)?.proxyUrl ?? (init as any)?.proxyUrl\n // Normalize init without proxyUrl leakage\n const cleanInit = { ...init } as any\n if (cleanInit.proxyUrl) delete cleanInit.proxyUrl\n if (!proxyUrl) {\n return fetch(input, { ...cleanInit, dispatcher: envAgent as any })\n }\n // Loopback bypass: per-account proxy must never intercept OAuth loopback callback (spec 1)\n const targetStr = getTargetUrlString(input)\n if (targetStr && isLoopbackUrl(targetStr)) {\n return fetch(input, cleanInit as RequestInit)\n }\n // Fast-fail pre-check (skip for env path)\n try {\n const reachable = await isProxyReachable(proxyUrl)\n if (!reachable) {\n const err = new Error(`[Proxy Fast-Fail] Proxy unreachable: ${proxyUrlForLogs(proxyUrl)}`) as Error & { code?: string; errorCode?: string; statusCode?: number }\n err.code = 'PROXY_UNREACHABLE'\n err.errorCode = 'proxy_unreachable'\n ;(err as any).statusCode = 503\n throw err\n }\n } catch (e) {\n if ((e as any)?.errorCode === 'proxy_unreachable') throw e\n // normalization failure -> throw\n if (e instanceof Error && e.message.startsWith('[proxy]')) throw e\n }\n\n let dispatcher: any\n try {\n dispatcher = await dispatcherForAsync(proxyUrl)\n } catch (e) {\n throw tagProxyUnreachable(e)\n }\n try {\n return await fetch(input, { ...cleanInit, dispatcher })\n } catch (err) {\n // Tag proxy unreachable so caller can classify\n throw tagProxyUnreachable(err)\n }\n}\n\n// Keep named export for tests that assert instanceof\nexport { envAgent as _envAgentForTest }\n"],"mappings":"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"}