@xopcai/xopc 0.0.100 → 0.0.101
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/dist/browser-ext/manifest.json +1 -1
- package/dist/extensions/telegram/xopc.extension.json +1 -1
- package/dist/extensions/weixin/src/api/api.js +5 -3
- package/dist/extensions/weixin/src/api/api.js.map +1 -1
- package/dist/gateway/static/root/assets/{agents-C5jtBK-H.js → agents-u8cmUfGQ.js} +2 -2
- package/dist/gateway/static/root/assets/{apps-page-smJl-qf_.js → apps-page-DF8m9cUE.js} +1 -1
- package/dist/gateway/static/root/assets/{attachment-preview-renderer-DBAxQXb-.js → attachment-preview-renderer-CybMSIoS.js} +2 -2
- package/dist/gateway/static/root/assets/{attachment-process-heavy-Csq3TrrP.js → attachment-process-heavy-RnGYy9bC.js} +2 -2
- package/dist/gateway/static/root/assets/{channels-settings-CH6KgFK6.js → channels-settings-BM3doRvi.js} +1 -1
- package/dist/gateway/static/root/assets/{channels-status-swr-CV70xvY7.js → channels-status-swr-ah4zWEIP.js} +1 -1
- package/dist/gateway/static/root/assets/{cron-api-CgBO8B7I.js → cron-api-DgMuL_2b.js} +1 -1
- package/dist/gateway/static/root/assets/{cron-page-Be0cW1cR.js → cron-page-COfUPKfq.js} +1 -1
- package/dist/gateway/static/root/assets/{dist-P8HRGUuh.js → dist-Dzy3Q0ZH.js} +1 -1
- package/dist/gateway/static/root/assets/{docx-preview-Dxj1EkCO.js → docx-preview-CajIk2FR.js} +1 -1
- package/dist/gateway/static/root/assets/{extension-debug-page-BowuycYu.js → extension-debug-page-kyxxefKh.js} +1 -1
- package/dist/gateway/static/root/assets/{extension-page-27-Zpv8O.js → extension-page-BVbCBHoI.js} +1 -1
- package/dist/gateway/static/root/assets/{extension-settings-page-DDFa_rIq.js → extension-settings-page-t1lb2A7v.js} +1 -1
- package/dist/gateway/static/root/assets/{field-primitives-BWLssxEb.js → field-primitives-6Vo1Yga4.js} +1 -1
- package/dist/gateway/static/root/assets/{heartbeat-config-api-BVuYNYT_.js → heartbeat-config-api-BF3psu4O.js} +1 -1
- package/dist/gateway/static/root/assets/{index-CRDAmr3W.js → index-WAyKAilR.js} +9 -9
- package/dist/gateway/static/root/assets/{logs-page-BDX8IrDQ.js → logs-page-BTVPXcHV.js} +1 -1
- package/dist/gateway/static/root/assets/{note-detail-page-BmQ9KeiA.js → note-detail-page-DqSAg_5Z.js} +1 -1
- package/dist/gateway/static/root/assets/{note-time-14I9Sgqh.js → note-time-FSOjchic.js} +1 -1
- package/dist/gateway/static/root/assets/{notes-page-BMfP0FFf.js → notes-page-DkRX7BE2.js} +1 -1
- package/dist/gateway/static/root/assets/{sessions-page-C3scyChq.js → sessions-page-Khdw35Zk.js} +1 -1
- package/dist/gateway/static/root/assets/{settings-advanced-gate-2ODuCjHP.js → settings-advanced-gate-DspC8oEm.js} +2 -2
- package/dist/gateway/static/root/assets/{settings-form-section-DUoGKAiW.js → settings-form-section-DuxvUuur.js} +1 -1
- package/dist/gateway/static/root/assets/{settings-page-CttAfOWV.js → settings-page-DN8JBZ8F.js} +2 -2
- package/dist/gateway/static/root/assets/{share-preview-page-B77xqiiI.js → share-preview-page-UNAcbiMb.js} +2 -2
- package/dist/gateway/static/root/assets/{skills-page-B_bAZGzB.js → skills-page-ByYT0fXZ.js} +1 -1
- package/dist/gateway/static/root/assets/{theme-store-BgCPrmxU.js → theme-store-B6KXPHgK.js} +1 -1
- package/dist/gateway/static/root/assets/{utils-CCGejlcX.js → utils-DK_1H47k.js} +1 -1
- package/dist/gateway/static/root/assets/{voice-api-key-field-DZzzihcY.js → voice-api-key-field-BYN2EIMs.js} +1 -1
- package/dist/gateway/static/root/assets/{workflow-page.utils-D5RMAmT4.js → workflow-page.utils-Bi19YNTr.js} +1 -1
- package/dist/gateway/static/root/assets/{workflows-page-DEYpC6Bv.js → workflows-page-DP57FEKW.js} +1 -1
- package/dist/gateway/static/root/index.html +2 -2
- package/dist/package.js +1 -1
- package/dist/src/agent/agent-manager.js +16 -0
- package/dist/src/agent/agent-manager.js.map +1 -1
- package/dist/src/agent/embedded/run-for-session.js +1 -0
- package/dist/src/agent/embedded/run-for-session.js.map +1 -1
- package/dist/src/auth/credentials.js +0 -1
- package/dist/src/auth/credentials.js.map +1 -1
- package/dist/src/cli/bin.d.ts +1 -0
- package/dist/src/cli/bin.js +1 -0
- package/dist/src/cli/bin.js.map +1 -1
- package/dist/src/gateway/heartbeat/service.js +1 -1
- package/dist/src/gateway/hono/oauth-async.js +36 -3
- package/dist/src/gateway/hono/oauth-async.js.map +1 -1
- package/dist/src/heartbeat/index.js +1 -1
- package/dist/src/infra/http-proxy-env.d.ts +4 -0
- package/dist/src/infra/http-proxy-env.js +23 -0
- package/dist/src/infra/http-proxy-env.js.map +1 -0
- package/package.json +3 -3
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { createLogger } from "../../utils/logger/index.js";
|
|
2
2
|
import { init_logger } from "../../utils/logger.js";
|
|
3
3
|
import { CredentialResolver, init_credentials } from "../../auth/credentials.js";
|
|
4
|
+
import { isLoopbackHost } from "../host.js";
|
|
5
|
+
import { resolveReverseProxyPublicUrl } from "../public-url.js";
|
|
4
6
|
import { anthropicOAuthProvider } from "../../auth/oauth/anthropic.js";
|
|
5
7
|
import { minimaxOAuthProvider } from "../../auth/oauth/minimax.js";
|
|
6
8
|
import { minimaxCnOAuthProvider } from "../../auth/oauth/minimax-cn.js";
|
|
@@ -19,8 +21,8 @@ import { Hono } from "hono";
|
|
|
19
21
|
* This allows OAuth flows that require user interaction (browser login)
|
|
20
22
|
* without blocking the HTTP request.
|
|
21
23
|
*/
|
|
22
|
-
init_logger();
|
|
23
24
|
init_credentials();
|
|
25
|
+
init_logger();
|
|
24
26
|
const log = createLogger("OAuthAsync");
|
|
25
27
|
/** User-facing message when undici/fetch fails (often DNS, firewall, or wrong machine for localhost callback). */
|
|
26
28
|
function formatOAuthAsyncError(err) {
|
|
@@ -61,6 +63,29 @@ function cancelOAuthSession(session, message = "OAuth flow cancelled") {
|
|
|
61
63
|
session.status = "cancelled";
|
|
62
64
|
session.message = message;
|
|
63
65
|
}
|
|
66
|
+
function hostWithoutPort(value) {
|
|
67
|
+
const raw = value?.split(",")[0]?.trim();
|
|
68
|
+
if (!raw) return void 0;
|
|
69
|
+
try {
|
|
70
|
+
return new URL(raw.includes("://") ? raw : `http://${raw}`).hostname.replace(/^\[/, "").replace(/\]$/, "");
|
|
71
|
+
} catch {
|
|
72
|
+
const unbracketed = raw.replace(/^\[/, "").replace(/\]$/, "");
|
|
73
|
+
return unbracketed.includes(":") && !unbracketed.includes(".") ? unbracketed : unbracketed.split(":")[0];
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
function requestLooksRemote(c) {
|
|
77
|
+
return [
|
|
78
|
+
hostWithoutPort(c.req.header("origin")),
|
|
79
|
+
hostWithoutPort(c.req.header("x-forwarded-host")),
|
|
80
|
+
hostWithoutPort(c.req.header("host"))
|
|
81
|
+
].some((candidate) => candidate && !isLoopbackHost(candidate));
|
|
82
|
+
}
|
|
83
|
+
function preferredOAuthLoginMethod(params) {
|
|
84
|
+
if (typeof params.requestedMethod === "string" && params.requestedMethod.trim()) return params.requestedMethod.trim();
|
|
85
|
+
if (params.provider !== "openai-codex") return;
|
|
86
|
+
const publicHost = hostWithoutPort(resolveReverseProxyPublicUrl(params.service.currentConfig) ?? void 0);
|
|
87
|
+
if (publicHost && !isLoopbackHost(publicHost) || requestLooksRemote(params.c)) return "device_code";
|
|
88
|
+
}
|
|
64
89
|
function createOAuthAsyncHandler(service) {
|
|
65
90
|
const oauth = new Hono();
|
|
66
91
|
/**
|
|
@@ -68,7 +93,8 @@ function createOAuthAsyncHandler(service) {
|
|
|
68
93
|
* Start async OAuth flow - returns immediately with session ID
|
|
69
94
|
*/
|
|
70
95
|
oauth.post("/start", async (c) => {
|
|
71
|
-
const
|
|
96
|
+
const body = await c.req.json().catch(() => ({}));
|
|
97
|
+
const { provider } = body;
|
|
72
98
|
if (!provider) return c.json({ error: "Provider is required" }, 400);
|
|
73
99
|
const oauthProvider = OAUTH_PROVIDERS[provider];
|
|
74
100
|
if (!oauthProvider) return c.json({ error: `Unknown OAuth provider: ${provider}` }, 400);
|
|
@@ -76,6 +102,12 @@ function createOAuthAsyncHandler(service) {
|
|
|
76
102
|
const session = {
|
|
77
103
|
id: sessionId,
|
|
78
104
|
provider,
|
|
105
|
+
preferredLoginMethod: preferredOAuthLoginMethod({
|
|
106
|
+
provider,
|
|
107
|
+
requestedMethod: body.loginMethod,
|
|
108
|
+
c,
|
|
109
|
+
service
|
|
110
|
+
}),
|
|
79
111
|
status: "pending",
|
|
80
112
|
createdAt: Date.now(),
|
|
81
113
|
expiresAt: Date.now() + SESSION_TTL_MS
|
|
@@ -230,9 +262,10 @@ async function runOAuthFlow(session, oauthProvider, _service) {
|
|
|
230
262
|
return "";
|
|
231
263
|
},
|
|
232
264
|
onSelect: async (prompt) => {
|
|
265
|
+
const preferredOption = session.preferredLoginMethod ? prompt.options.find((option) => option.id === session.preferredLoginMethod) : void 0;
|
|
233
266
|
const browserOption = prompt.options.find((option) => option.id === "browser");
|
|
234
267
|
const firstOption = prompt.options[0];
|
|
235
|
-
const selectedOption = browserOption ?? firstOption;
|
|
268
|
+
const selectedOption = preferredOption ?? browserOption ?? firstOption;
|
|
236
269
|
if (!selectedOption) throw new Error("OAuth login did not provide any selectable auth method");
|
|
237
270
|
log.debug({
|
|
238
271
|
sessionId: session.id,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"oauth-async.js","names":["googleGeminiCliOAuthProvider","googleAntigravityOAuthProvider"],"sources":["../../../../src/gateway/hono/oauth-async.ts"],"sourcesContent":["/**\n * Async OAuth Handler\n * \n * Provides non-blocking OAuth flow with session-based state management.\n * This allows OAuth flows that require user interaction (browser login) \n * without blocking the HTTP request.\n */\n\nimport { Hono } from 'hono';\nimport type { GatewayService } from '../service.js';\nimport { \n type OAuthProviderInterface, \n type OAuthLoginCallbacks,\n type OAuthCredentials \n} from '../../auth/oauth/types.js';\nimport {\n kimiCodingOAuthProvider,\n minimaxOAuthProvider,\n minimaxCnOAuthProvider,\n anthropicOAuthProvider,\n githubCopilotOAuthProvider,\n googleGeminiCliOAuthProvider,\n googleAntigravityOAuthProvider,\n openaiCodexOAuthProvider,\n} from '../../auth/oauth/index.js';\nimport { createLogger } from '../../utils/logger.js';\nimport { CredentialResolver } from '../../auth/credentials.js';\n\nconst log = createLogger('OAuthAsync');\n\n/** User-facing message when undici/fetch fails (often DNS, firewall, or wrong machine for localhost callback). */\nfunction formatOAuthAsyncError(err: unknown): string {\n\tconst base = err instanceof Error ? err.message : 'OAuth login failed';\n\tconst cause =\n\t\terr instanceof Error && err.cause instanceof Error\n\t\t\t? err.cause.message\n\t\t\t: err instanceof Error && typeof err.cause === 'string'\n\t\t\t\t? err.cause\n\t\t\t\t: '';\n\tconst detail = cause ? ` (${cause})` : '';\n\tif (/^fetch failed$/i.test(base) || base.includes('fetch failed')) {\n\t\treturn (\n\t\t\t`Network request failed${detail}. If the browser opened on another device, the redirect goes to that device's localhost — ` +\n\t\t\t`copy the full URL from the browser address bar after sign-in (starts with http://127.0.0.1 or http://localhost) and paste it below. ` +\n\t\t\t`Otherwise check VPN/proxy/DNS/firewall access to Google OAuth.`\n\t\t);\n\t}\n\treturn base;\n}\n\n// Static OAuth providers map\nconst OAUTH_PROVIDERS: Record<string, OAuthProviderInterface> = {\n 'kimi-coding': kimiCodingOAuthProvider,\n 'minimax': minimaxOAuthProvider,\n 'minimax-cn': minimaxCnOAuthProvider,\n 'anthropic': anthropicOAuthProvider,\n 'github-copilot': githubCopilotOAuthProvider,\n 'google-gemini-cli': googleGeminiCliOAuthProvider,\n 'google-antigravity': googleAntigravityOAuthProvider,\n 'openai-codex': openaiCodexOAuthProvider,\n};\n\n// OAuth session state\ninterface OAuthSession {\n id: string;\n provider: string;\n status: 'pending' | 'waiting_auth' | 'waiting_code' | 'completed' | 'failed' | 'cancelled';\n authUrl?: string;\n instructions?: string;\n deviceCode?: string;\n verificationUri?: string;\n message?: string;\n error?: string;\n credentials?: OAuthCredentials;\n createdAt: number;\n expiresAt: number;\n abortController?: AbortController;\n manualCodeResolve?: (code: string) => void;\n manualCodeReject?: (error: Error) => void;\n}\n\n// In-memory session store (could be moved to Redis for production)\nconst oauthSessions = new Map<string, OAuthSession>();\nconst SESSION_TTL_MS = 10 * 60 * 1000; // 10 minutes\n\n// Clean up expired sessions periodically\nsetInterval(() => {\n const now = Date.now();\n for (const [id, session] of oauthSessions.entries()) {\n if (now > session.expiresAt) {\n cancelOAuthSession(session, 'OAuth flow expired');\n oauthSessions.delete(id);\n log.debug({ sessionId: id }, 'Cleaned up expired OAuth session');\n }\n }\n}, 60 * 1000);\n\nfunction generateSessionId(): string {\n return `oauth_${Date.now()}_${Math.random().toString(36).substring(2, 15)}`;\n}\n\nfunction cancelOAuthSession(session: OAuthSession, message = 'OAuth flow cancelled'): void {\n if (session.abortController) {\n session.abortController.abort();\n }\n\n if (session.manualCodeResolve) {\n session.manualCodeResolve('');\n }\n\n session.manualCodeResolve = undefined;\n session.manualCodeReject = undefined;\n session.status = 'cancelled';\n session.message = message;\n}\n\nexport function createOAuthAsyncHandler(service: GatewayService) {\n const oauth = new Hono();\n\n /**\n * POST /api/auth/oauth-async/start\n * Start async OAuth flow - returns immediately with session ID\n */\n oauth.post('/start', async (c) => {\n const { provider } = await c.req.json().catch(() => ({}));\n \n if (!provider) {\n return c.json({ error: 'Provider is required' }, 400);\n }\n\n const oauthProvider = OAUTH_PROVIDERS[provider];\n if (!oauthProvider) {\n return c.json({ error: `Unknown OAuth provider: ${provider}` }, 400);\n }\n\n const sessionId = generateSessionId();\n const session: OAuthSession = {\n id: sessionId,\n provider,\n status: 'pending',\n createdAt: Date.now(),\n expiresAt: Date.now() + SESSION_TTL_MS,\n };\n\n oauthSessions.set(sessionId, session);\n\n // Start OAuth flow in background\n runOAuthFlow(session, oauthProvider, service).catch(err => {\n log.error({ sessionId, provider, error: err }, 'Background OAuth flow failed');\n session.status = 'failed';\n session.error = err instanceof Error ? err.message : 'OAuth flow failed';\n });\n\n return c.json({ \n ok: true, \n payload: { \n sessionId,\n provider,\n status: session.status,\n } \n });\n });\n\n /**\n * GET /api/auth/oauth-async/:sessionId/status\n * Check OAuth session status\n */\n oauth.get('/:sessionId/status', (c) => {\n const sessionId = c.req.param('sessionId');\n const session = oauthSessions.get(sessionId);\n\n if (!session) {\n return c.json({ error: 'Session not found' }, 404);\n }\n\n return c.json({ \n ok: true, \n payload: { \n sessionId: session.id,\n provider: session.provider,\n status: session.status,\n authUrl: session.authUrl,\n instructions: session.instructions,\n deviceCode: session.deviceCode,\n verificationUri: session.verificationUri,\n message: session.message,\n error: session.error,\n expiresAt: session.expiresAt,\n } \n });\n });\n\n /**\n * POST /api/auth/oauth-async/:sessionId/code\n * Submit manual authorization code\n */\n oauth.post('/:sessionId/code', async (c) => {\n const sessionId = c.req.param('sessionId');\n const { code } = await c.req.json().catch(() => ({}));\n \n if (!code) {\n return c.json({ error: 'Code is required' }, 400);\n }\n\n const session = oauthSessions.get(sessionId);\n if (!session) {\n return c.json({ error: 'Session not found' }, 404);\n }\n\n if (session.status !== 'waiting_code' || !session.manualCodeResolve) {\n return c.json({ error: 'Session is not waiting for code' }, 400);\n }\n\n // Resolve the manual code promise\n session.manualCodeResolve(code);\n session.manualCodeResolve = undefined;\n session.manualCodeReject = undefined;\n\n return c.json({ \n ok: true, \n payload: { \n message: 'Code submitted, processing...',\n } \n });\n });\n\n /**\n * POST /api/auth/oauth-async/:sessionId/cancel\n * Cancel OAuth flow\n */\n oauth.post('/:sessionId/cancel', async (c) => {\n const sessionId = c.req.param('sessionId');\n const session = oauthSessions.get(sessionId);\n\n if (!session) {\n return c.json({ error: 'Session not found' }, 404);\n }\n\n cancelOAuthSession(session);\n\n return c.json({ \n ok: true, \n payload: { \n message: 'OAuth flow cancelled',\n } \n });\n });\n\n /**\n * DELETE /api/auth/oauth-async/:sessionId\n * Clean up OAuth session\n */\n oauth.delete('/:sessionId', (c) => {\n const sessionId = c.req.param('sessionId');\n \n if (oauthSessions.has(sessionId)) {\n const session = oauthSessions.get(sessionId)!;\n cancelOAuthSession(session);\n oauthSessions.delete(sessionId);\n }\n\n return c.json({ ok: true });\n });\n\n return oauth;\n}\n\n/**\n * Run OAuth flow in background\n */\nasync function runOAuthFlow(\n session: OAuthSession,\n oauthProvider: OAuthProviderInterface,\n _service: GatewayService\n): Promise<void> {\n const abortController = new AbortController();\n session.abortController = abortController;\n\n let manualCodePromise: Promise<string> | null = null;\n let manualCodeResolve: ((code: string) => void) | undefined;\n let manualCodeReject: ((error: Error) => void) | undefined;\n\n const callbacks: OAuthLoginCallbacks = {\n onAuth: (auth: { url: string; instructions?: string }) => {\n session.authUrl = auth.url;\n session.instructions = auth.instructions;\n \n if (oauthProvider.usesCallbackServer) {\n // For callback server providers, prepare for manual code input\n session.status = 'waiting_code';\n session.message = 'Complete authorization in browser, or paste the redirect URL below';\n manualCodePromise = new Promise((resolve, reject) => {\n manualCodeResolve = resolve;\n manualCodeReject = reject;\n });\n session.manualCodeResolve = manualCodeResolve;\n session.manualCodeReject = manualCodeReject;\n } else {\n session.status = 'waiting_auth';\n session.message = 'Complete authorization in browser';\n }\n },\n onDeviceCode: (info) => {\n session.status = 'waiting_auth';\n session.authUrl = info.verificationUri;\n session.deviceCode = info.userCode;\n session.verificationUri = info.verificationUri;\n session.instructions = `Enter code ${info.userCode}`;\n session.message = `Open ${info.verificationUri} and enter code ${info.userCode}`;\n },\n onPrompt: async (prompt: { message: string; deviceCode?: string; verificationUri?: string }) => {\n session.status = 'waiting_code';\n session.deviceCode = prompt.deviceCode;\n session.verificationUri = prompt.verificationUri;\n session.message = prompt.message;\n \n // For device code flow, wait for manual input\n manualCodePromise = new Promise((resolve, reject) => {\n manualCodeResolve = resolve;\n manualCodeReject = reject;\n });\n session.manualCodeResolve = manualCodeResolve;\n session.manualCodeReject = manualCodeReject;\n \n // Return empty for now, will be resolved by manual code submission\n return '';\n },\n onProgress: (message: string) => {\n log.debug({ sessionId: session.id, message }, 'OAuth progress');\n session.message = message;\n },\n onManualCodeInput: async () => {\n // Return the manual code promise for callback server providers\n if (manualCodePromise) {\n return manualCodePromise;\n }\n return '';\n },\n onSelect: async (prompt) => {\n const browserOption = prompt.options.find((option) => option.id === 'browser');\n const firstOption = prompt.options[0];\n const selectedOption = browserOption ?? firstOption;\n if (!selectedOption) {\n throw new Error('OAuth login did not provide any selectable auth method');\n }\n log.debug(\n { sessionId: session.id, provider: session.provider, selected: selectedOption.id },\n 'Selected OAuth auth method',\n );\n return selectedOption.id;\n },\n signal: abortController.signal,\n };\n\n try {\n const credentials = await oauthProvider.login(callbacks);\n \n // Save credentials to cache and persist them as first-class OAuth credentials.\n setOAuthCredentialsToCache(session.provider, credentials);\n\n const resolver = new CredentialResolver();\n await resolver.saveOAuthToken(session.provider, {\n access: oauthProvider.getApiKey(credentials),\n refresh: credentials.refresh,\n expiresAt: credentials.expires,\n scope: Array.isArray(credentials.scope) ? credentials.scope.filter((value): value is string => typeof value === 'string') : undefined,\n createdAt: new Date().toISOString(),\n });\n\n session.status = 'completed';\n session.credentials = credentials;\n session.message = 'OAuth login successful';\n \n log.info({ sessionId: session.id, provider: session.provider }, 'OAuth login completed');\n } catch (err) {\n if (abortController.signal.aborted || session.status === 'cancelled') {\n session.status = 'cancelled';\n session.message ??= 'OAuth flow cancelled by user';\n } else {\n session.status = 'failed';\n session.error = formatOAuthAsyncError(err);\n log.error({ sessionId: session.id, provider: session.provider, error: err }, 'OAuth login failed');\n }\n } finally {\n session.abortController = undefined;\n session.manualCodeResolve = undefined;\n session.manualCodeReject = undefined;\n }\n}\n\n// Simple in-memory cache for OAuth credentials\nconst oauthCredentialsCache: Map<string, OAuthCredentials> = new Map();\n\nexport function getOAuthCredentialsFromCache(provider: string): OAuthCredentials | undefined {\n return oauthCredentialsCache.get(provider);\n}\n\nexport function setOAuthCredentialsToCache(provider: string, creds: OAuthCredentials): void {\n oauthCredentialsCache.set(provider, creds);\n}\n\nexport function deleteOAuthCredentialsFromCache(provider: string): void {\n oauthCredentialsCache.delete(provider);\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"oauth-async.js","names":["googleGeminiCliOAuthProvider","googleAntigravityOAuthProvider"],"sources":["../../../../src/gateway/hono/oauth-async.ts"],"sourcesContent":["/**\n * Async OAuth Handler\n * \n * Provides non-blocking OAuth flow with session-based state management.\n * This allows OAuth flows that require user interaction (browser login) \n * without blocking the HTTP request.\n */\n\nimport { Hono } from 'hono';\nimport type { Context } from 'hono';\nimport type { GatewayService } from '../service.js';\nimport { \n type OAuthProviderInterface, \n type OAuthLoginCallbacks,\n type OAuthCredentials \n} from '../../auth/oauth/types.js';\nimport {\n kimiCodingOAuthProvider,\n minimaxOAuthProvider,\n minimaxCnOAuthProvider,\n anthropicOAuthProvider,\n githubCopilotOAuthProvider,\n googleGeminiCliOAuthProvider,\n googleAntigravityOAuthProvider,\n openaiCodexOAuthProvider,\n} from '../../auth/oauth/index.js';\nimport { CredentialResolver } from '../../auth/credentials.js';\nimport { isLoopbackHost } from '../host.js';\nimport { resolveReverseProxyPublicUrl } from '../public-url.js';\nimport { createLogger } from '../../utils/logger.js';\n\nconst log = createLogger('OAuthAsync');\n\n/** User-facing message when undici/fetch fails (often DNS, firewall, or wrong machine for localhost callback). */\nfunction formatOAuthAsyncError(err: unknown): string {\n\tconst base = err instanceof Error ? err.message : 'OAuth login failed';\n\tconst cause =\n\t\terr instanceof Error && err.cause instanceof Error\n\t\t\t? err.cause.message\n\t\t\t: err instanceof Error && typeof err.cause === 'string'\n\t\t\t\t? err.cause\n\t\t\t\t: '';\n\tconst detail = cause ? ` (${cause})` : '';\n\tif (/^fetch failed$/i.test(base) || base.includes('fetch failed')) {\n\t\treturn (\n\t\t\t`Network request failed${detail}. If the browser opened on another device, the redirect goes to that device's localhost — ` +\n\t\t\t`copy the full URL from the browser address bar after sign-in (starts with http://127.0.0.1 or http://localhost) and paste it below. ` +\n\t\t\t`Otherwise check VPN/proxy/DNS/firewall access to Google OAuth.`\n\t\t);\n\t}\n\treturn base;\n}\n\n// Static OAuth providers map\nconst OAUTH_PROVIDERS: Record<string, OAuthProviderInterface> = {\n 'kimi-coding': kimiCodingOAuthProvider,\n 'minimax': minimaxOAuthProvider,\n 'minimax-cn': minimaxCnOAuthProvider,\n 'anthropic': anthropicOAuthProvider,\n 'github-copilot': githubCopilotOAuthProvider,\n 'google-gemini-cli': googleGeminiCliOAuthProvider,\n 'google-antigravity': googleAntigravityOAuthProvider,\n 'openai-codex': openaiCodexOAuthProvider,\n};\n\n// OAuth session state\ninterface OAuthSession {\n id: string;\n provider: string;\n preferredLoginMethod?: string;\n status: 'pending' | 'waiting_auth' | 'waiting_code' | 'completed' | 'failed' | 'cancelled';\n authUrl?: string;\n instructions?: string;\n deviceCode?: string;\n verificationUri?: string;\n message?: string;\n error?: string;\n credentials?: OAuthCredentials;\n createdAt: number;\n expiresAt: number;\n abortController?: AbortController;\n manualCodeResolve?: (code: string) => void;\n manualCodeReject?: (error: Error) => void;\n}\n\n// In-memory session store (could be moved to Redis for production)\nconst oauthSessions = new Map<string, OAuthSession>();\nconst SESSION_TTL_MS = 10 * 60 * 1000; // 10 minutes\n\n// Clean up expired sessions periodically\nsetInterval(() => {\n const now = Date.now();\n for (const [id, session] of oauthSessions.entries()) {\n if (now > session.expiresAt) {\n cancelOAuthSession(session, 'OAuth flow expired');\n oauthSessions.delete(id);\n log.debug({ sessionId: id }, 'Cleaned up expired OAuth session');\n }\n }\n}, 60 * 1000);\n\nfunction generateSessionId(): string {\n return `oauth_${Date.now()}_${Math.random().toString(36).substring(2, 15)}`;\n}\n\nfunction cancelOAuthSession(session: OAuthSession, message = 'OAuth flow cancelled'): void {\n if (session.abortController) {\n session.abortController.abort();\n }\n\n if (session.manualCodeResolve) {\n session.manualCodeResolve('');\n }\n\n session.manualCodeResolve = undefined;\n session.manualCodeReject = undefined;\n session.status = 'cancelled';\n session.message = message;\n}\n\nfunction hostWithoutPort(value: string | undefined): string | undefined {\n const raw = value?.split(',')[0]?.trim();\n if (!raw) return undefined;\n\n try {\n return new URL(raw.includes('://') ? raw : `http://${raw}`).hostname.replace(/^\\[/, '').replace(/\\]$/, '');\n } catch {\n const unbracketed = raw.replace(/^\\[/, '').replace(/\\]$/, '');\n return unbracketed.includes(':') && !unbracketed.includes('.') ? unbracketed : unbracketed.split(':')[0];\n }\n}\n\nfunction requestLooksRemote(c: Context): boolean {\n const originHost = hostWithoutPort(c.req.header('origin'));\n const forwardedHost = hostWithoutPort(c.req.header('x-forwarded-host'));\n const host = hostWithoutPort(c.req.header('host'));\n\n return [originHost, forwardedHost, host].some((candidate) => candidate && !isLoopbackHost(candidate));\n}\n\nfunction preferredOAuthLoginMethod(params: {\n provider: string;\n requestedMethod?: unknown;\n c: Context;\n service: GatewayService;\n}): string | undefined {\n if (typeof params.requestedMethod === 'string' && params.requestedMethod.trim()) {\n return params.requestedMethod.trim();\n }\n\n if (params.provider !== 'openai-codex') {\n return undefined;\n }\n\n const publicUrl = resolveReverseProxyPublicUrl(params.service.currentConfig);\n const publicHost = hostWithoutPort(publicUrl ?? undefined);\n if ((publicHost && !isLoopbackHost(publicHost)) || requestLooksRemote(params.c)) {\n return 'device_code';\n }\n\n return undefined;\n}\n\nexport function createOAuthAsyncHandler(service: GatewayService) {\n const oauth = new Hono();\n\n /**\n * POST /api/auth/oauth-async/start\n * Start async OAuth flow - returns immediately with session ID\n */\n oauth.post('/start', async (c) => {\n const body = await c.req.json().catch(() => ({}));\n const { provider } = body;\n \n if (!provider) {\n return c.json({ error: 'Provider is required' }, 400);\n }\n\n const oauthProvider = OAUTH_PROVIDERS[provider];\n if (!oauthProvider) {\n return c.json({ error: `Unknown OAuth provider: ${provider}` }, 400);\n }\n\n const sessionId = generateSessionId();\n const session: OAuthSession = {\n id: sessionId,\n provider,\n preferredLoginMethod: preferredOAuthLoginMethod({\n provider,\n requestedMethod: body.loginMethod,\n c,\n service,\n }),\n status: 'pending',\n createdAt: Date.now(),\n expiresAt: Date.now() + SESSION_TTL_MS,\n };\n\n oauthSessions.set(sessionId, session);\n\n // Start OAuth flow in background\n runOAuthFlow(session, oauthProvider, service).catch(err => {\n log.error({ sessionId, provider, error: err }, 'Background OAuth flow failed');\n session.status = 'failed';\n session.error = err instanceof Error ? err.message : 'OAuth flow failed';\n });\n\n return c.json({ \n ok: true, \n payload: { \n sessionId,\n provider,\n status: session.status,\n } \n });\n });\n\n /**\n * GET /api/auth/oauth-async/:sessionId/status\n * Check OAuth session status\n */\n oauth.get('/:sessionId/status', (c) => {\n const sessionId = c.req.param('sessionId');\n const session = oauthSessions.get(sessionId);\n\n if (!session) {\n return c.json({ error: 'Session not found' }, 404);\n }\n\n return c.json({ \n ok: true, \n payload: { \n sessionId: session.id,\n provider: session.provider,\n status: session.status,\n authUrl: session.authUrl,\n instructions: session.instructions,\n deviceCode: session.deviceCode,\n verificationUri: session.verificationUri,\n message: session.message,\n error: session.error,\n expiresAt: session.expiresAt,\n } \n });\n });\n\n /**\n * POST /api/auth/oauth-async/:sessionId/code\n * Submit manual authorization code\n */\n oauth.post('/:sessionId/code', async (c) => {\n const sessionId = c.req.param('sessionId');\n const { code } = await c.req.json().catch(() => ({}));\n \n if (!code) {\n return c.json({ error: 'Code is required' }, 400);\n }\n\n const session = oauthSessions.get(sessionId);\n if (!session) {\n return c.json({ error: 'Session not found' }, 404);\n }\n\n if (session.status !== 'waiting_code' || !session.manualCodeResolve) {\n return c.json({ error: 'Session is not waiting for code' }, 400);\n }\n\n // Resolve the manual code promise\n session.manualCodeResolve(code);\n session.manualCodeResolve = undefined;\n session.manualCodeReject = undefined;\n\n return c.json({ \n ok: true, \n payload: { \n message: 'Code submitted, processing...',\n } \n });\n });\n\n /**\n * POST /api/auth/oauth-async/:sessionId/cancel\n * Cancel OAuth flow\n */\n oauth.post('/:sessionId/cancel', async (c) => {\n const sessionId = c.req.param('sessionId');\n const session = oauthSessions.get(sessionId);\n\n if (!session) {\n return c.json({ error: 'Session not found' }, 404);\n }\n\n cancelOAuthSession(session);\n\n return c.json({ \n ok: true, \n payload: { \n message: 'OAuth flow cancelled',\n } \n });\n });\n\n /**\n * DELETE /api/auth/oauth-async/:sessionId\n * Clean up OAuth session\n */\n oauth.delete('/:sessionId', (c) => {\n const sessionId = c.req.param('sessionId');\n \n if (oauthSessions.has(sessionId)) {\n const session = oauthSessions.get(sessionId)!;\n cancelOAuthSession(session);\n oauthSessions.delete(sessionId);\n }\n\n return c.json({ ok: true });\n });\n\n return oauth;\n}\n\n/**\n * Run OAuth flow in background\n */\nasync function runOAuthFlow(\n session: OAuthSession,\n oauthProvider: OAuthProviderInterface,\n _service: GatewayService\n): Promise<void> {\n const abortController = new AbortController();\n session.abortController = abortController;\n\n let manualCodePromise: Promise<string> | null = null;\n let manualCodeResolve: ((code: string) => void) | undefined;\n let manualCodeReject: ((error: Error) => void) | undefined;\n\n const callbacks: OAuthLoginCallbacks = {\n onAuth: (auth: { url: string; instructions?: string }) => {\n session.authUrl = auth.url;\n session.instructions = auth.instructions;\n \n if (oauthProvider.usesCallbackServer) {\n // For callback server providers, prepare for manual code input\n session.status = 'waiting_code';\n session.message = 'Complete authorization in browser, or paste the redirect URL below';\n manualCodePromise = new Promise((resolve, reject) => {\n manualCodeResolve = resolve;\n manualCodeReject = reject;\n });\n session.manualCodeResolve = manualCodeResolve;\n session.manualCodeReject = manualCodeReject;\n } else {\n session.status = 'waiting_auth';\n session.message = 'Complete authorization in browser';\n }\n },\n onDeviceCode: (info) => {\n session.status = 'waiting_auth';\n session.authUrl = info.verificationUri;\n session.deviceCode = info.userCode;\n session.verificationUri = info.verificationUri;\n session.instructions = `Enter code ${info.userCode}`;\n session.message = `Open ${info.verificationUri} and enter code ${info.userCode}`;\n },\n onPrompt: async (prompt: { message: string; deviceCode?: string; verificationUri?: string }) => {\n session.status = 'waiting_code';\n session.deviceCode = prompt.deviceCode;\n session.verificationUri = prompt.verificationUri;\n session.message = prompt.message;\n \n // For device code flow, wait for manual input\n manualCodePromise = new Promise((resolve, reject) => {\n manualCodeResolve = resolve;\n manualCodeReject = reject;\n });\n session.manualCodeResolve = manualCodeResolve;\n session.manualCodeReject = manualCodeReject;\n \n // Return empty for now, will be resolved by manual code submission\n return '';\n },\n onProgress: (message: string) => {\n log.debug({ sessionId: session.id, message }, 'OAuth progress');\n session.message = message;\n },\n onManualCodeInput: async () => {\n // Return the manual code promise for callback server providers\n if (manualCodePromise) {\n return manualCodePromise;\n }\n return '';\n },\n onSelect: async (prompt) => {\n const preferredOption = session.preferredLoginMethod\n ? prompt.options.find((option) => option.id === session.preferredLoginMethod)\n : undefined;\n const browserOption = prompt.options.find((option) => option.id === 'browser');\n const firstOption = prompt.options[0];\n const selectedOption = preferredOption ?? browserOption ?? firstOption;\n if (!selectedOption) {\n throw new Error('OAuth login did not provide any selectable auth method');\n }\n log.debug(\n { sessionId: session.id, provider: session.provider, selected: selectedOption.id },\n 'Selected OAuth auth method',\n );\n return selectedOption.id;\n },\n signal: abortController.signal,\n };\n\n try {\n const credentials = await oauthProvider.login(callbacks);\n \n // Save credentials to cache and persist them as first-class OAuth credentials.\n setOAuthCredentialsToCache(session.provider, credentials);\n\n const resolver = new CredentialResolver();\n await resolver.saveOAuthToken(session.provider, {\n access: oauthProvider.getApiKey(credentials),\n refresh: credentials.refresh,\n expiresAt: credentials.expires,\n scope: Array.isArray(credentials.scope) ? credentials.scope.filter((value): value is string => typeof value === 'string') : undefined,\n createdAt: new Date().toISOString(),\n });\n\n session.status = 'completed';\n session.credentials = credentials;\n session.message = 'OAuth login successful';\n \n log.info({ sessionId: session.id, provider: session.provider }, 'OAuth login completed');\n } catch (err) {\n if (abortController.signal.aborted || session.status === 'cancelled') {\n session.status = 'cancelled';\n session.message ??= 'OAuth flow cancelled by user';\n } else {\n session.status = 'failed';\n session.error = formatOAuthAsyncError(err);\n log.error({ sessionId: session.id, provider: session.provider, error: err }, 'OAuth login failed');\n }\n } finally {\n session.abortController = undefined;\n session.manualCodeResolve = undefined;\n session.manualCodeReject = undefined;\n }\n}\n\n// Simple in-memory cache for OAuth credentials\nconst oauthCredentialsCache: Map<string, OAuthCredentials> = new Map();\n\nexport function getOAuthCredentialsFromCache(provider: string): OAuthCredentials | undefined {\n return oauthCredentialsCache.get(provider);\n}\n\nexport function setOAuthCredentialsToCache(provider: string, creds: OAuthCredentials): void {\n oauthCredentialsCache.set(provider, creds);\n}\n\nexport function deleteOAuthCredentialsFromCache(provider: string): void {\n oauthCredentialsCache.delete(provider);\n}\n"],"mappings":"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"}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { DEFAULT_ACK_MAX_CHARS, HEARTBEAT_OK, NO_REPLY, shouldSilence, stripHeartbeatToken } from "./tokens.js";
|
|
2
|
-
import { appendCronEventLines } from "./event-prompt.js";
|
|
3
2
|
import { isWithinActiveHours } from "./active-hours.js";
|
|
4
3
|
import { isHeartbeatContentEmpty } from "./content-check.js";
|
|
4
|
+
import { appendCronEventLines } from "./event-prompt.js";
|
|
5
5
|
import { createHeartbeatWake } from "./wake.js";
|
|
6
6
|
export { DEFAULT_ACK_MAX_CHARS, HEARTBEAT_OK, NO_REPLY, appendCronEventLines, createHeartbeatWake, isHeartbeatContentEmpty, isWithinActiveHours, shouldSilence, stripHeartbeatToken };
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { EnvHttpProxyAgent, setGlobalDispatcher } from "undici";
|
|
2
|
+
//#region src/infra/http-proxy-env.ts
|
|
3
|
+
const PROXY_ENV_KEYS = [
|
|
4
|
+
"http_proxy",
|
|
5
|
+
"HTTP_PROXY",
|
|
6
|
+
"https_proxy",
|
|
7
|
+
"HTTPS_PROXY",
|
|
8
|
+
"no_proxy",
|
|
9
|
+
"NO_PROXY"
|
|
10
|
+
];
|
|
11
|
+
function hasProxyEnv(env = process.env) {
|
|
12
|
+
return PROXY_ENV_KEYS.some((key) => typeof env[key] === "string" && env[key].trim().length > 0);
|
|
13
|
+
}
|
|
14
|
+
function installGlobalProxyDispatcherFromEnv(env = process.env) {
|
|
15
|
+
if (!hasProxyEnv(env)) return false;
|
|
16
|
+
setGlobalDispatcher(new EnvHttpProxyAgent());
|
|
17
|
+
return true;
|
|
18
|
+
}
|
|
19
|
+
installGlobalProxyDispatcherFromEnv();
|
|
20
|
+
//#endregion
|
|
21
|
+
export { hasProxyEnv, installGlobalProxyDispatcherFromEnv };
|
|
22
|
+
|
|
23
|
+
//# sourceMappingURL=http-proxy-env.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"http-proxy-env.js","names":[],"sources":["../../../src/infra/http-proxy-env.ts"],"sourcesContent":["import { EnvHttpProxyAgent, setGlobalDispatcher } from 'undici';\n\nconst PROXY_ENV_KEYS = [\n 'http_proxy',\n 'HTTP_PROXY',\n 'https_proxy',\n 'HTTPS_PROXY',\n 'no_proxy',\n 'NO_PROXY',\n] as const;\n\ntype ProxyEnv = Record<string, string | undefined>;\n\nexport function hasProxyEnv(env: ProxyEnv = process.env): boolean {\n return PROXY_ENV_KEYS.some((key) => typeof env[key] === 'string' && env[key]!.trim().length > 0);\n}\n\nexport function installGlobalProxyDispatcherFromEnv(env: ProxyEnv = process.env): boolean {\n if (!hasProxyEnv(env)) {\n return false;\n }\n\n setGlobalDispatcher(new EnvHttpProxyAgent());\n return true;\n}\n\ninstallGlobalProxyDispatcherFromEnv();\n"],"mappings":";;AAEA,MAAM,iBAAiB;CACrB;CACA;CACA;CACA;CACA;CACA;CACD;AAID,SAAgB,YAAY,MAAgB,QAAQ,KAAc;AAChE,QAAO,eAAe,MAAM,QAAQ,OAAO,IAAI,SAAS,YAAY,IAAI,KAAM,MAAM,CAAC,SAAS,EAAE;;AAGlG,SAAgB,oCAAoC,MAAgB,QAAQ,KAAc;AACxF,KAAI,CAAC,YAAY,IAAI,CACnB,QAAO;AAGT,qBAAoB,IAAI,mBAAmB,CAAC;AAC5C,QAAO;;AAGT,qCAAqC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@xopcai/xopc",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.101",
|
|
4
4
|
"description": "The OPC workstation that grows with you: AI assistant for One Person Companies — CLI, gateway, multi-channel (Telegram/WeChat), 20+ LLM providers via pi-ai, extensions and skills.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/src/index.js",
|
|
@@ -78,7 +78,7 @@
|
|
|
78
78
|
"electron-updater": "^6.8.3",
|
|
79
79
|
"glob": "^13.0.6",
|
|
80
80
|
"grammy": "^1.43.0",
|
|
81
|
-
"hono": "^4.12.
|
|
81
|
+
"hono": "^4.12.25",
|
|
82
82
|
"jiti": "^2.7.0",
|
|
83
83
|
"js-yaml": "^4.1.1",
|
|
84
84
|
"jsdom": "^29.1.1",
|
|
@@ -121,7 +121,7 @@
|
|
|
121
121
|
"tsx": "^4.22.3",
|
|
122
122
|
"typescript": "^6.0.3",
|
|
123
123
|
"typescript-eslint": "^8.60.0",
|
|
124
|
-
"vite": "^8.0.
|
|
124
|
+
"vite": "^8.0.16",
|
|
125
125
|
"vitepress": "^1.6.4",
|
|
126
126
|
"vitest": "4.1.5",
|
|
127
127
|
"vue": "^3.5.35"
|