@undefineds.co/xpod 0.1.6 → 0.2.0-preview.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +141 -2
- package/config/cli.json +9 -71
- package/config/cloud.json +34 -7
- package/config/local.json +6 -2
- package/config/resolver.json +11 -49
- package/config/runtime-open.json +22 -0
- package/config/xpod.base.json +32 -0
- package/config/xpod.cluster.json +2 -44
- package/config/xpod.json +5 -2
- package/dist/api/ApiServer.js +1 -1
- package/dist/api/ApiServer.js.map +1 -1
- package/dist/api/auth/AuthContext.d.ts +12 -1
- package/dist/api/auth/AuthContext.js +18 -1
- package/dist/api/auth/AuthContext.js.map +1 -1
- package/dist/api/auth/ClientCredentialsAuthenticator.d.ts +0 -1
- package/dist/api/auth/ClientCredentialsAuthenticator.js.map +1 -1
- package/dist/api/auth/ServiceTokenAuthenticator.d.ts +18 -0
- package/dist/api/auth/ServiceTokenAuthenticator.js +50 -0
- package/dist/api/auth/ServiceTokenAuthenticator.js.map +1 -0
- package/dist/api/auth/index.d.ts +1 -0
- package/dist/api/auth/index.js +1 -0
- package/dist/api/auth/index.js.map +1 -1
- package/dist/api/chatkit/ai-provider.d.ts +0 -10
- package/dist/api/chatkit/ai-provider.js +11 -120
- package/dist/api/chatkit/ai-provider.js.map +1 -1
- package/dist/api/chatkit/default-agent.js +11 -8
- package/dist/api/chatkit/default-agent.js.map +1 -1
- package/dist/api/chatkit/pod-store.js +19 -3
- package/dist/api/chatkit/pod-store.js.map +1 -1
- package/dist/api/chatkit/schema.d.ts +9 -3
- package/dist/api/chatkit/schema.js +14 -6
- package/dist/api/chatkit/schema.js.map +1 -1
- package/dist/api/container/business-token.d.ts +9 -0
- package/dist/api/container/business-token.js +32 -0
- package/dist/api/container/business-token.js.map +1 -0
- package/dist/api/container/cloud.js +36 -12
- package/dist/api/container/cloud.js.map +1 -1
- package/dist/api/container/common.js +12 -5
- package/dist/api/container/common.js.map +1 -1
- package/dist/api/container/index.js +94 -14
- package/dist/api/container/index.js.map +1 -1
- package/dist/api/container/local.js +2 -1
- package/dist/api/container/local.js.map +1 -1
- package/dist/api/container/routes.js +81 -15
- package/dist/api/container/routes.js.map +1 -1
- package/dist/api/container/types.d.ts +8 -6
- package/dist/api/container/types.js.map +1 -1
- package/dist/api/handlers/AdminHandler.js +9 -9
- package/dist/api/handlers/AdminHandler.js.map +1 -1
- package/dist/api/handlers/ApiKeyHandler.js +0 -6
- package/dist/api/handlers/ApiKeyHandler.js.map +1 -1
- package/dist/api/handlers/EdgeNodeSignalHandler.d.ts +17 -0
- package/dist/api/handlers/EdgeNodeSignalHandler.js +171 -0
- package/dist/api/handlers/EdgeNodeSignalHandler.js.map +1 -0
- package/dist/api/handlers/PodManagementHandler.d.ts +5 -4
- package/dist/api/handlers/PodManagementHandler.js +11 -10
- package/dist/api/handlers/PodManagementHandler.js.map +1 -1
- package/dist/api/handlers/ProvisionHandler.d.ts +42 -0
- package/dist/api/handlers/ProvisionHandler.js +161 -0
- package/dist/api/handlers/ProvisionHandler.js.map +1 -0
- package/dist/api/handlers/QuotaHandler.d.ts +7 -7
- package/dist/api/handlers/QuotaHandler.js +143 -73
- package/dist/api/handlers/QuotaHandler.js.map +1 -1
- package/dist/api/handlers/SubdomainClientHandler.js +2 -2
- package/dist/api/handlers/SubdomainClientHandler.js.map +1 -1
- package/dist/api/handlers/SubdomainHandler.js +13 -8
- package/dist/api/handlers/SubdomainHandler.js.map +1 -1
- package/dist/api/handlers/UsageHandler.d.ts +14 -0
- package/dist/api/handlers/UsageHandler.js +123 -0
- package/dist/api/handlers/UsageHandler.js.map +1 -0
- package/dist/api/handlers/index.d.ts +3 -1
- package/dist/api/handlers/index.js +3 -1
- package/dist/api/handlers/index.js.map +1 -1
- package/dist/api/main.js +18 -0
- package/dist/api/main.js.map +1 -1
- package/dist/api/middleware/OpenAuthMiddleware.d.ts +12 -0
- package/dist/api/middleware/OpenAuthMiddleware.js +27 -0
- package/dist/api/middleware/OpenAuthMiddleware.js.map +1 -0
- package/dist/api/runtime.d.ts +15 -0
- package/dist/api/runtime.js +104 -0
- package/dist/api/runtime.js.map +1 -0
- package/dist/api/service/VercelChatService.d.ts +16 -7
- package/dist/api/service/VercelChatService.js +98 -178
- package/dist/api/service/VercelChatService.js.map +1 -1
- package/dist/api/store/DrizzleClientCredentialsStore.d.ts +6 -11
- package/dist/api/store/DrizzleClientCredentialsStore.js +9 -39
- package/dist/api/store/DrizzleClientCredentialsStore.js.map +1 -1
- package/dist/authorization/AuthModeSelector.d.ts +10 -0
- package/dist/authorization/AuthModeSelector.js +27 -0
- package/dist/authorization/AuthModeSelector.js.map +1 -0
- package/dist/authorization/AuthModeSelector.jsonld +81 -0
- package/dist/cli/commands/account.d.ts +6 -0
- package/dist/cli/commands/account.js +119 -0
- package/dist/cli/commands/account.js.map +1 -0
- package/dist/cli/commands/auth.js +20 -29
- package/dist/cli/commands/auth.js.map +1 -1
- package/dist/cli/commands/backup.d.ts +15 -0
- package/dist/cli/commands/backup.js +286 -0
- package/dist/cli/commands/backup.js.map +1 -0
- package/dist/cli/commands/config.d.ts +34 -3
- package/dist/cli/commands/config.js +195 -258
- package/dist/cli/commands/config.js.map +1 -1
- package/dist/cli/commands/doctor.d.ts +6 -0
- package/dist/cli/commands/doctor.js +94 -0
- package/dist/cli/commands/doctor.js.map +1 -0
- package/dist/cli/commands/pod.d.ts +6 -0
- package/dist/cli/commands/pod.js +124 -0
- package/dist/cli/commands/pod.js.map +1 -0
- package/dist/cli/commands/start.js +28 -5
- package/dist/cli/commands/start.js.map +1 -1
- package/dist/cli/index.js +9 -0
- package/dist/cli/index.js.map +1 -1
- package/dist/cli/lib/credentials-store.d.ts +17 -0
- package/dist/cli/lib/credentials-store.js +73 -0
- package/dist/cli/lib/credentials-store.js.map +1 -0
- package/dist/cli/lib/css-account.d.ts +17 -0
- package/dist/cli/lib/css-account.js +56 -0
- package/dist/cli/lib/css-account.js.map +1 -1
- package/dist/cli/lib/pod-thread-store.d.ts +57 -0
- package/dist/cli/lib/pod-thread-store.js +310 -0
- package/dist/cli/lib/pod-thread-store.js.map +1 -0
- package/dist/cli/lib/solid-auth.d.ts +20 -0
- package/dist/cli/lib/solid-auth.js +70 -0
- package/dist/cli/lib/solid-auth.js.map +1 -0
- package/dist/components/components.jsonld +5 -8
- package/dist/components/context.jsonld +114 -244
- package/dist/edge/EdgeNodeAgent.js +2 -2
- package/dist/edge/EdgeNodeAgent.js.map +1 -1
- package/dist/edge/EdgeNodeDnsCoordinator.d.ts +1 -7
- package/dist/edge/EdgeNodeDnsCoordinator.js +31 -41
- package/dist/edge/EdgeNodeDnsCoordinator.js.map +1 -1
- package/dist/edge/EdgeNodeDnsCoordinator.jsonld +1 -27
- package/dist/edge/EdgeNodeModeDetector.d.ts +1 -1
- package/dist/edge/EdgeNodeModeDetector.js +9 -11
- package/dist/edge/EdgeNodeModeDetector.js.map +1 -1
- package/dist/http/ClusterIngressRouter.js +3 -3
- package/dist/http/ClusterIngressRouter.js.map +1 -1
- package/dist/http/ClusterWebSocketConfigurator.js +2 -2
- package/dist/http/ClusterWebSocketConfigurator.js.map +1 -1
- package/dist/http/PodRoutingHttpHandler.js +2 -2
- package/dist/http/PodRoutingHttpHandler.js.map +1 -1
- package/dist/http/cluster/PodMigrationHttpHandler.d.ts +1 -1
- package/dist/http/cluster/PodMigrationHttpHandler.js +1 -1
- package/dist/http/cluster/PodMigrationHttpHandler.js.map +1 -1
- package/dist/identity/drizzle/EdgeNodeRepository.d.ts +37 -4
- package/dist/identity/drizzle/EdgeNodeRepository.js +120 -128
- package/dist/identity/drizzle/EdgeNodeRepository.js.map +1 -1
- package/dist/identity/drizzle/ServiceTokenRepository.d.ts +52 -0
- package/dist/identity/drizzle/ServiceTokenRepository.js +143 -0
- package/dist/identity/drizzle/ServiceTokenRepository.js.map +1 -0
- package/dist/identity/drizzle/db.d.ts +9 -0
- package/dist/identity/drizzle/db.js +208 -1
- package/dist/identity/drizzle/db.js.map +1 -1
- package/dist/identity/drizzle/schema.pg.d.ts +5 -0
- package/dist/identity/drizzle/schema.pg.js +49 -20
- package/dist/identity/drizzle/schema.pg.js.map +1 -1
- package/dist/identity/drizzle/schema.sqlite.d.ts +332 -57
- package/dist/identity/drizzle/schema.sqlite.js +48 -18
- package/dist/identity/drizzle/schema.sqlite.js.map +1 -1
- package/dist/identity/oidc/AutoDetectIdentityProviderHandler.js +6 -4
- package/dist/identity/oidc/AutoDetectIdentityProviderHandler.js.map +1 -1
- package/dist/index.d.ts +6 -9
- package/dist/index.js +12 -14
- package/dist/index.js.map +1 -1
- package/dist/main.js +25 -8
- package/dist/main.js.map +1 -1
- package/dist/provision/ProvisionCodeCodec.d.ts +39 -0
- package/dist/provision/ProvisionCodeCodec.js +65 -0
- package/dist/provision/ProvisionCodeCodec.js.map +1 -0
- package/dist/provision/ProvisionCodeCodec.jsonld +47 -0
- package/dist/provision/ProvisionPodCreator.d.ts +20 -0
- package/dist/provision/ProvisionPodCreator.js +84 -0
- package/dist/provision/ProvisionPodCreator.js.map +1 -0
- package/dist/provision/ProvisionPodCreator.jsonld +118 -0
- package/dist/quota/DrizzleQuotaService.d.ts +17 -3
- package/dist/quota/DrizzleQuotaService.js +108 -8
- package/dist/quota/DrizzleQuotaService.js.map +1 -1
- package/dist/quota/DrizzleQuotaService.jsonld +33 -22
- package/dist/quota/NoopQuotaService.d.ts +7 -1
- package/dist/quota/NoopQuotaService.js +12 -0
- package/dist/quota/NoopQuotaService.js.map +1 -1
- package/dist/quota/NoopQuotaService.jsonld +24 -0
- package/dist/quota/QuotaService.d.ts +17 -0
- package/dist/quota/QuotaService.js +5 -0
- package/dist/quota/QuotaService.js.map +1 -1
- package/dist/quota/QuotaService.jsonld +50 -0
- package/dist/runtime/Proxy.d.ts +22 -4
- package/dist/runtime/Proxy.js +154 -35
- package/dist/runtime/Proxy.js.map +1 -1
- package/dist/runtime/XpodRuntime.d.ts +49 -0
- package/dist/runtime/XpodRuntime.js +374 -0
- package/dist/runtime/XpodRuntime.js.map +1 -0
- package/dist/runtime/env-utils.d.ts +2 -0
- package/dist/runtime/env-utils.js +55 -0
- package/dist/runtime/env-utils.js.map +1 -0
- package/dist/runtime/index.d.ts +4 -0
- package/dist/runtime/index.js +8 -1
- package/dist/runtime/index.js.map +1 -1
- package/dist/runtime/socket-fetch.d.ts +1 -0
- package/dist/runtime/socket-fetch.js +72 -0
- package/dist/runtime/socket-fetch.js.map +1 -0
- package/dist/runtime/socket-http.d.ts +1 -0
- package/dist/runtime/socket-http.js +142 -0
- package/dist/runtime/socket-http.js.map +1 -0
- package/dist/runtime/socket-utils.d.ts +2 -0
- package/dist/runtime/socket-utils.js +34 -0
- package/dist/runtime/socket-utils.js.map +1 -0
- package/dist/service/{EdgeNodeHeartbeatService.d.ts → EdgeNodeSignalClient.d.ts} +3 -3
- package/dist/service/{EdgeNodeHeartbeatService.js → EdgeNodeSignalClient.js} +4 -4
- package/dist/service/EdgeNodeSignalClient.js.map +1 -0
- package/dist/service/PodMigrationService.d.ts +1 -2
- package/dist/service/PodMigrationService.js +1 -2
- package/dist/service/PodMigrationService.js.map +1 -1
- package/dist/storage/SparqlUpdateResourceStore.js +1 -1
- package/dist/storage/SparqlUpdateResourceStore.js.map +1 -1
- package/dist/storage/accessors/MinioDataAccessor.d.ts +6 -0
- package/dist/storage/accessors/MinioDataAccessor.js +10 -0
- package/dist/storage/accessors/MinioDataAccessor.js.map +1 -1
- package/dist/storage/accessors/MinioDataAccessor.jsonld +4 -0
- package/dist/storage/accessors/MixDataAccessor.d.ts +2 -1
- package/dist/storage/accessors/MixDataAccessor.js +12 -1
- package/dist/storage/accessors/MixDataAccessor.js.map +1 -1
- package/dist/storage/accessors/MixDataAccessor.jsonld +19 -0
- package/dist/storage/locking/UrlAwareRedisLocker.d.ts +18 -0
- package/dist/storage/locking/UrlAwareRedisLocker.js +60 -0
- package/dist/storage/locking/UrlAwareRedisLocker.js.map +1 -0
- package/dist/storage/locking/UrlAwareRedisLocker.jsonld +123 -0
- package/dist/storage/quota/UsageRepository.d.ts +41 -8
- package/dist/storage/quota/UsageRepository.js +252 -50
- package/dist/storage/quota/UsageRepository.js.map +1 -1
- package/dist/storage/sparql/ComunicaQuintEngine.d.ts +9 -0
- package/dist/storage/sparql/ComunicaQuintEngine.js +50 -9
- package/dist/storage/sparql/ComunicaQuintEngine.js.map +1 -1
- package/dist/storage/sparql/QueryOptimizer.js +13 -1
- package/dist/storage/sparql/QueryOptimizer.js.map +1 -1
- package/dist/storage/sparql/QuintQuerySource.d.ts +14 -0
- package/dist/storage/sparql/QuintQuerySource.js +152 -1
- package/dist/storage/sparql/QuintQuerySource.js.map +1 -1
- package/dist/storage/sparql/SubgraphQueryEngine.d.ts +1 -0
- package/dist/storage/sparql/SubgraphQueryEngine.js +6 -2
- package/dist/storage/sparql/SubgraphQueryEngine.js.map +1 -1
- package/dist/storage/sparql/SubgraphQueryEngine.jsonld +4 -0
- package/dist/subdomain/SubdomainClient.d.ts +3 -3
- package/dist/subdomain/SubdomainClient.js +1 -1
- package/dist/subdomain/SubdomainClient.js.map +1 -1
- package/dist/subdomain/SubdomainService.d.ts +15 -16
- package/dist/subdomain/SubdomainService.js +80 -54
- package/dist/subdomain/SubdomainService.js.map +1 -1
- package/dist/subdomain/SubdomainService.jsonld +22 -26
- package/dist/supervisor/Supervisor.d.ts +7 -2
- package/dist/supervisor/Supervisor.js +33 -1
- package/dist/supervisor/Supervisor.js.map +1 -1
- package/dist/test-utils/index.d.ts +4 -0
- package/dist/test-utils/index.js +8 -0
- package/dist/test-utils/index.js.map +1 -0
- package/dist/test-utils/no-auth-xpod.d.ts +11 -0
- package/dist/test-utils/no-auth-xpod.js +25 -0
- package/dist/test-utils/no-auth-xpod.js.map +1 -0
- package/dist/test-utils/seed-pod.d.ts +5 -0
- package/dist/test-utils/seed-pod.js +61 -0
- package/dist/test-utils/seed-pod.js.map +1 -0
- package/package.json +23 -5
- package/templates/identity/account/create-pod.html.ejs +110 -0
- package/templates/main.html.ejs +10 -0
- package/dist/api/handlers/DevHandler.d.ts +0 -18
- package/dist/api/handlers/DevHandler.js +0 -276
- package/dist/api/handlers/DevHandler.js.map +0 -1
- package/dist/api/handlers/SignalHandler.d.ts +0 -13
- package/dist/api/handlers/SignalHandler.js +0 -122
- package/dist/api/handlers/SignalHandler.js.map +0 -1
- package/dist/gateway/Proxy.d.ts +0 -24
- package/dist/gateway/Proxy.js +0 -209
- package/dist/gateway/Proxy.js.map +0 -1
- package/dist/gateway/Supervisor.d.ts +0 -2
- package/dist/gateway/Supervisor.js +0 -7
- package/dist/gateway/Supervisor.js.map +0 -1
- package/dist/gateway/port-finder.d.ts +0 -4
- package/dist/gateway/port-finder.js +0 -15
- package/dist/gateway/port-finder.js.map +0 -1
- package/dist/gateway/types.d.ts +0 -1
- package/dist/gateway/types.js +0 -3
- package/dist/gateway/types.js.map +0 -1
- package/dist/http/SignalInterceptHttpHandler.d.ts +0 -24
- package/dist/http/SignalInterceptHttpHandler.js +0 -47
- package/dist/http/SignalInterceptHttpHandler.js.map +0 -1
- package/dist/http/SignalInterceptHttpHandler.jsonld +0 -103
- package/dist/http/admin/EdgeNodeSignalHttpHandler.d.ts +0 -71
- package/dist/http/admin/EdgeNodeSignalHttpHandler.js +0 -674
- package/dist/http/admin/EdgeNodeSignalHttpHandler.js.map +0 -1
- package/dist/http/admin/EdgeNodeSignalHttpHandler.jsonld +0 -406
- package/dist/http/cluster/PodMigrationHttpHandler.jsonld +0 -169
- package/dist/quota/DefaultQuotaService.d.ts +0 -16
- package/dist/quota/DefaultQuotaService.js +0 -37
- package/dist/quota/DefaultQuotaService.js.map +0 -1
- package/dist/quota/DefaultQuotaService.jsonld +0 -85
- package/dist/service/EdgeNodeHeartbeatService.js.map +0 -1
- package/dist/service/PodMigrationService.jsonld +0 -76
- package/dist/storage/MigratableDataAccessor.d.ts +0 -63
- package/dist/storage/MigratableDataAccessor.js +0 -11
- package/dist/storage/MigratableDataAccessor.js.map +0 -1
- package/dist/storage/MigratableDataAccessor.jsonld +0 -60
- package/dist/storage/accessors/TieredMinioDataAccessor.d.ts +0 -150
- package/dist/storage/accessors/TieredMinioDataAccessor.js +0 -582
- package/dist/storage/accessors/TieredMinioDataAccessor.js.map +0 -1
- package/dist/storage/accessors/TieredMinioDataAccessor.jsonld +0 -333
- package/static/app/assets/index.css +0 -1
- package/static/app/assets/main.js +0 -11
package/config/xpod.json
CHANGED
|
@@ -89,9 +89,12 @@
|
|
|
89
89
|
}
|
|
90
90
|
},
|
|
91
91
|
{
|
|
92
|
-
"comment": "
|
|
92
|
+
"comment": "Quota service backed by identity DB, no env defaults = unlimited",
|
|
93
93
|
"@id": "urn:undefineds:xpod:QuotaService",
|
|
94
|
-
"@type": "
|
|
94
|
+
"@type": "DrizzleQuotaService",
|
|
95
|
+
"DrizzleQuotaService:_options_identityDbUrl": {
|
|
96
|
+
"@id": "urn:solid-server:default:variable:identityDbUrl"
|
|
97
|
+
}
|
|
95
98
|
},
|
|
96
99
|
{
|
|
97
100
|
"@id": "urn:solid-server:default:variable:rootFilePath",
|
package/dist/api/ApiServer.js
CHANGED
|
@@ -183,7 +183,7 @@ class ApiServer {
|
|
|
183
183
|
response.setHeader('Vary', 'Origin');
|
|
184
184
|
}
|
|
185
185
|
response.setHeader('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, PATCH, OPTIONS');
|
|
186
|
-
response.setHeader('Access-Control-Allow-Headers', '
|
|
186
|
+
response.setHeader('Access-Control-Allow-Headers', '*');
|
|
187
187
|
response.setHeader('Access-Control-Max-Age', '86400');
|
|
188
188
|
}
|
|
189
189
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ApiServer.js","sourceRoot":"","sources":["../../src/api/ApiServer.ts"],"names":[],"mappings":"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","sourcesContent":["import { createServer, type Server, type IncomingMessage, type ServerResponse } from 'node:http';\nimport { getLoggerFor } from 'global-logger-factory';\nimport type { AuthMiddleware, AuthenticatedRequest } from './middleware/AuthMiddleware';\n\n/**\n * Route handler function\n */\nexport type RouteHandler = (\n request: AuthenticatedRequest,\n response: ServerResponse,\n params: Record<string, string>,\n) => Promise<void>;\n\n/**\n * Route definition\n */\nexport interface Route {\n method: string;\n pattern: RegExp;\n paramNames: string[];\n handler: RouteHandler;\n /** If true, skip authentication */\n public?: boolean;\n}\n\nexport interface ApiServerOptions {\n port: number;\n host?: string;\n authMiddleware: AuthMiddleware;\n corsOrigins?: string[];\n}\n\n/**\n * Standalone API Server\n */\nexport class ApiServer {\n private readonly logger = getLoggerFor(this);\n private readonly port: number;\n private readonly host: string;\n private readonly authMiddleware: AuthMiddleware;\n private readonly corsOrigins: string[];\n private readonly routes: Route[] = [];\n private server?: Server;\n\n public constructor(options: ApiServerOptions) {\n this.port = options.port;\n this.host = options.host ?? '0.0.0.0';\n this.authMiddleware = options.authMiddleware;\n this.corsOrigins = options.corsOrigins ?? ['*'];\n }\n\n /**\n * Register a route\n */\n public route(\n method: string,\n path: string,\n handler: RouteHandler,\n options?: {\n /** If true, skip authentication for this route */\n public?: boolean;\n },\n ): void {\n const { pattern, paramNames } = this.pathToRegex(path);\n this.routes.push({\n method: method.toUpperCase(),\n pattern,\n paramNames,\n handler,\n public: options?.public,\n });\n this.logger.debug(`Registered route: ${method.toUpperCase()} ${path}${options?.public ? ' (public)' : ''}`);\n }\n\n /**\n * Convenience methods for common HTTP methods\n */\n public get(path: string, handler: RouteHandler, options?: { public?: boolean }): void {\n this.route('GET', path, handler, options);\n }\n\n public post(path: string, handler: RouteHandler, options?: { public?: boolean }): void {\n this.route('POST', path, handler, options);\n }\n\n public put(path: string, handler: RouteHandler, options?: { public?: boolean }): void {\n this.route('PUT', path, handler, options);\n }\n\n public delete(path: string, handler: RouteHandler, options?: { public?: boolean }): void {\n this.route('DELETE', path, handler, options);\n }\n\n public patch(path: string, handler: RouteHandler, options?: { public?: boolean }): void {\n this.route('PATCH', path, handler, options);\n }\n\n /**\n * Start the server\n */\n public async start(): Promise<void> {\n return new Promise((resolve, reject) => {\n this.server = createServer((req, res) => {\n this.handleRequest(req, res).catch((error) => {\n this.logger.error(`Unhandled error: ${error}`);\n if (!res.headersSent) {\n res.statusCode = 500;\n res.setHeader('Content-Type', 'application/json');\n res.end(JSON.stringify({ error: 'Internal Server Error' }));\n }\n });\n });\n\n this.server.on('error', reject);\n\n this.server.listen(this.port, this.host, () => {\n this.logger.info(`API Server listening on ${this.host}:${this.port}`);\n resolve();\n });\n });\n }\n\n /**\n * Stop the server\n */\n public async stop(): Promise<void> {\n return new Promise((resolve, reject) => {\n if (!this.server) {\n resolve();\n return;\n }\n\n this.server.close((error) => {\n if (error) {\n reject(error);\n } else {\n this.logger.info('API Server stopped');\n resolve();\n }\n });\n });\n }\n\n /**\n * Get the underlying HTTP server (for WebSocket upgrade)\n */\n public getHttpServer(): Server | undefined {\n return this.server;\n }\n\n private async handleRequest(request: IncomingMessage, response: ServerResponse): Promise<void> {\n const method = request.method?.toUpperCase() ?? 'GET';\n const url = new URL(request.url ?? '/', `http://${request.headers.host ?? 'localhost'}`);\n const path = url.pathname;\n\n // Handle CORS preflight\n if (method === 'OPTIONS') {\n this.handleCors(request, response);\n response.statusCode = 204;\n response.end();\n return;\n }\n\n // Add CORS headers\n this.handleCors(request, response);\n\n // Find matching route\n const match = this.findRoute(method, path);\n if (!match) {\n response.statusCode = 404;\n response.setHeader('Content-Type', 'application/json');\n response.end(JSON.stringify({ error: 'Not Found' }));\n return;\n }\n\n const { route, params } = match;\n const authRequest = request as AuthenticatedRequest;\n\n // Run auth middleware unless route is public\n if (!route.public) {\n const authOk = await this.authMiddleware.process(authRequest, response);\n if (!authOk) {\n return;\n }\n }\n\n // Execute handler\n try {\n await route.handler(authRequest, response, params);\n } catch (error) {\n this.logger.error(`Route handler error: ${error}`);\n if (!response.headersSent) {\n response.statusCode = 500;\n response.setHeader('Content-Type', 'application/json');\n response.end(JSON.stringify({ error: 'Internal Server Error' }));\n }\n }\n }\n\n private findRoute(method: string, path: string): { route: Route; params: Record<string, string> } | undefined {\n for (const route of this.routes) {\n if (route.method !== method) {\n continue;\n }\n\n const match = route.pattern.exec(path);\n if (match) {\n const params: Record<string, string> = {};\n route.paramNames.forEach((name, index) => {\n params[name] = match[index + 1];\n });\n return { route, params };\n }\n }\n return undefined;\n }\n\n private pathToRegex(path: string): { pattern: RegExp; paramNames: string[] } {\n const paramNames: string[] = [];\n let regexStr = path\n // 先处理通配符 *path 或 * (匹配剩余所有路径)\n .replace(/\\*([a-zA-Z0-9_]*)/g, (_, name) => {\n paramNames.push(name || 'wildcard');\n return '(.*)';\n })\n // 再处理普通参数 :param (只匹配单段)\n .replace(/:([a-zA-Z0-9_]+)/g, (_, name) => {\n paramNames.push(name);\n return '([^/]+)';\n })\n .replace(/\\//g, '\\\\/');\n return {\n pattern: new RegExp(`^${regexStr}$`),\n paramNames,\n };\n }\n\n private handleCors(request: IncomingMessage, response: ServerResponse): void {\n const origin = request.headers.origin;\n\n if (this.corsOrigins.includes('*')) {\n response.setHeader('Access-Control-Allow-Origin', '*');\n } else if (origin && this.corsOrigins.includes(origin)) {\n response.setHeader('Access-Control-Allow-Origin', origin);\n response.setHeader('Vary', 'Origin');\n }\n\n response.setHeader('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, PATCH, OPTIONS');\n response.setHeader('Access-Control-Allow-Headers', 'Authorization, Content-Type, DPoP');\n response.setHeader('Access-Control-Max-Age', '86400');\n }\n}\n"]}
|
|
1
|
+
{"version":3,"file":"ApiServer.js","sourceRoot":"","sources":["../../src/api/ApiServer.ts"],"names":[],"mappings":"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","sourcesContent":["import { createServer, type Server, type IncomingMessage, type ServerResponse } from 'node:http';\nimport { getLoggerFor } from 'global-logger-factory';\nimport type { AuthMiddleware, AuthenticatedRequest } from './middleware/AuthMiddleware';\n\n/**\n * Route handler function\n */\nexport type RouteHandler = (\n request: AuthenticatedRequest,\n response: ServerResponse,\n params: Record<string, string>,\n) => Promise<void>;\n\n/**\n * Route definition\n */\nexport interface Route {\n method: string;\n pattern: RegExp;\n paramNames: string[];\n handler: RouteHandler;\n /** If true, skip authentication */\n public?: boolean;\n}\n\nexport interface ApiServerOptions {\n port: number;\n host?: string;\n authMiddleware: AuthMiddleware;\n corsOrigins?: string[];\n}\n\n/**\n * Standalone API Server\n */\nexport class ApiServer {\n private readonly logger = getLoggerFor(this);\n private readonly port: number;\n private readonly host: string;\n private readonly authMiddleware: AuthMiddleware;\n private readonly corsOrigins: string[];\n private readonly routes: Route[] = [];\n private server?: Server;\n\n public constructor(options: ApiServerOptions) {\n this.port = options.port;\n this.host = options.host ?? '0.0.0.0';\n this.authMiddleware = options.authMiddleware;\n this.corsOrigins = options.corsOrigins ?? ['*'];\n }\n\n /**\n * Register a route\n */\n public route(\n method: string,\n path: string,\n handler: RouteHandler,\n options?: {\n /** If true, skip authentication for this route */\n public?: boolean;\n },\n ): void {\n const { pattern, paramNames } = this.pathToRegex(path);\n this.routes.push({\n method: method.toUpperCase(),\n pattern,\n paramNames,\n handler,\n public: options?.public,\n });\n this.logger.debug(`Registered route: ${method.toUpperCase()} ${path}${options?.public ? ' (public)' : ''}`);\n }\n\n /**\n * Convenience methods for common HTTP methods\n */\n public get(path: string, handler: RouteHandler, options?: { public?: boolean }): void {\n this.route('GET', path, handler, options);\n }\n\n public post(path: string, handler: RouteHandler, options?: { public?: boolean }): void {\n this.route('POST', path, handler, options);\n }\n\n public put(path: string, handler: RouteHandler, options?: { public?: boolean }): void {\n this.route('PUT', path, handler, options);\n }\n\n public delete(path: string, handler: RouteHandler, options?: { public?: boolean }): void {\n this.route('DELETE', path, handler, options);\n }\n\n public patch(path: string, handler: RouteHandler, options?: { public?: boolean }): void {\n this.route('PATCH', path, handler, options);\n }\n\n /**\n * Start the server\n */\n public async start(): Promise<void> {\n return new Promise((resolve, reject) => {\n this.server = createServer((req, res) => {\n this.handleRequest(req, res).catch((error) => {\n this.logger.error(`Unhandled error: ${error}`);\n if (!res.headersSent) {\n res.statusCode = 500;\n res.setHeader('Content-Type', 'application/json');\n res.end(JSON.stringify({ error: 'Internal Server Error' }));\n }\n });\n });\n\n this.server.on('error', reject);\n\n this.server.listen(this.port, this.host, () => {\n this.logger.info(`API Server listening on ${this.host}:${this.port}`);\n resolve();\n });\n });\n }\n\n /**\n * Stop the server\n */\n public async stop(): Promise<void> {\n return new Promise((resolve, reject) => {\n if (!this.server) {\n resolve();\n return;\n }\n\n this.server.close((error) => {\n if (error) {\n reject(error);\n } else {\n this.logger.info('API Server stopped');\n resolve();\n }\n });\n });\n }\n\n /**\n * Get the underlying HTTP server (for WebSocket upgrade)\n */\n public getHttpServer(): Server | undefined {\n return this.server;\n }\n\n private async handleRequest(request: IncomingMessage, response: ServerResponse): Promise<void> {\n const method = request.method?.toUpperCase() ?? 'GET';\n const url = new URL(request.url ?? '/', `http://${request.headers.host ?? 'localhost'}`);\n const path = url.pathname;\n\n // Handle CORS preflight\n if (method === 'OPTIONS') {\n this.handleCors(request, response);\n response.statusCode = 204;\n response.end();\n return;\n }\n\n // Add CORS headers\n this.handleCors(request, response);\n\n // Find matching route\n const match = this.findRoute(method, path);\n if (!match) {\n response.statusCode = 404;\n response.setHeader('Content-Type', 'application/json');\n response.end(JSON.stringify({ error: 'Not Found' }));\n return;\n }\n\n const { route, params } = match;\n const authRequest = request as AuthenticatedRequest;\n\n // Run auth middleware unless route is public\n if (!route.public) {\n const authOk = await this.authMiddleware.process(authRequest, response);\n if (!authOk) {\n return;\n }\n }\n\n // Execute handler\n try {\n await route.handler(authRequest, response, params);\n } catch (error) {\n this.logger.error(`Route handler error: ${error}`);\n if (!response.headersSent) {\n response.statusCode = 500;\n response.setHeader('Content-Type', 'application/json');\n response.end(JSON.stringify({ error: 'Internal Server Error' }));\n }\n }\n }\n\n private findRoute(method: string, path: string): { route: Route; params: Record<string, string> } | undefined {\n for (const route of this.routes) {\n if (route.method !== method) {\n continue;\n }\n\n const match = route.pattern.exec(path);\n if (match) {\n const params: Record<string, string> = {};\n route.paramNames.forEach((name, index) => {\n params[name] = match[index + 1];\n });\n return { route, params };\n }\n }\n return undefined;\n }\n\n private pathToRegex(path: string): { pattern: RegExp; paramNames: string[] } {\n const paramNames: string[] = [];\n let regexStr = path\n // 先处理通配符 *path 或 * (匹配剩余所有路径)\n .replace(/\\*([a-zA-Z0-9_]*)/g, (_, name) => {\n paramNames.push(name || 'wildcard');\n return '(.*)';\n })\n // 再处理普通参数 :param (只匹配单段)\n .replace(/:([a-zA-Z0-9_]+)/g, (_, name) => {\n paramNames.push(name);\n return '([^/]+)';\n })\n .replace(/\\//g, '\\\\/');\n return {\n pattern: new RegExp(`^${regexStr}$`),\n paramNames,\n };\n }\n\n private handleCors(request: IncomingMessage, response: ServerResponse): void {\n const origin = request.headers.origin;\n\n if (this.corsOrigins.includes('*')) {\n response.setHeader('Access-Control-Allow-Origin', '*');\n } else if (origin && this.corsOrigins.includes(origin)) {\n response.setHeader('Access-Control-Allow-Origin', origin);\n response.setHeader('Vary', 'Origin');\n }\n\n response.setHeader('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, PATCH, OPTIONS');\n response.setHeader('Access-Control-Allow-Headers', '*');\n response.setHeader('Access-Control-Max-Age', '86400');\n }\n}\n"]}
|
|
@@ -23,7 +23,13 @@ export interface NodeAuthContext {
|
|
|
23
23
|
nodeId: string;
|
|
24
24
|
accountId?: string;
|
|
25
25
|
}
|
|
26
|
-
export
|
|
26
|
+
export interface ServiceAuthContext {
|
|
27
|
+
type: 'service';
|
|
28
|
+
serviceType: 'local' | 'business' | 'cloud' | 'compute';
|
|
29
|
+
serviceId: string;
|
|
30
|
+
scopes: string[];
|
|
31
|
+
}
|
|
32
|
+
export type AuthContext = SolidAuthContext | NodeAuthContext | ServiceAuthContext;
|
|
27
33
|
export declare function isSolidAuth(ctx: AuthContext): ctx is SolidAuthContext;
|
|
28
34
|
export declare function isNodeAuth(ctx: AuthContext): ctx is NodeAuthContext;
|
|
29
35
|
/**
|
|
@@ -39,3 +45,8 @@ export declare function getDisplayName(ctx: AuthContext): string | undefined;
|
|
|
39
45
|
*/
|
|
40
46
|
export declare function getAccountId(ctx: AuthContext): string | undefined;
|
|
41
47
|
export declare function getNodeId(ctx: AuthContext): string | undefined;
|
|
48
|
+
export declare function isServiceAuth(ctx: AuthContext): ctx is ServiceAuthContext;
|
|
49
|
+
/**
|
|
50
|
+
* Check if a service auth context has the required scope
|
|
51
|
+
*/
|
|
52
|
+
export declare function hasScope(ctx: AuthContext, scope: string): boolean;
|
|
@@ -14,6 +14,8 @@ exports.getWebId = getWebId;
|
|
|
14
14
|
exports.getDisplayName = getDisplayName;
|
|
15
15
|
exports.getAccountId = getAccountId;
|
|
16
16
|
exports.getNodeId = getNodeId;
|
|
17
|
+
exports.isServiceAuth = isServiceAuth;
|
|
18
|
+
exports.hasScope = hasScope;
|
|
17
19
|
function isSolidAuth(ctx) {
|
|
18
20
|
return ctx.type === 'solid';
|
|
19
21
|
}
|
|
@@ -36,9 +38,24 @@ function getDisplayName(ctx) {
|
|
|
36
38
|
* Get accountId from auth context (if available)
|
|
37
39
|
*/
|
|
38
40
|
function getAccountId(ctx) {
|
|
39
|
-
|
|
41
|
+
if (ctx.type === 'solid') {
|
|
42
|
+
return ctx.accountId;
|
|
43
|
+
}
|
|
44
|
+
if (ctx.type === 'node') {
|
|
45
|
+
return ctx.accountId;
|
|
46
|
+
}
|
|
47
|
+
return undefined;
|
|
40
48
|
}
|
|
41
49
|
function getNodeId(ctx) {
|
|
42
50
|
return ctx.type === 'node' ? ctx.nodeId : undefined;
|
|
43
51
|
}
|
|
52
|
+
function isServiceAuth(ctx) {
|
|
53
|
+
return ctx.type === 'service';
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Check if a service auth context has the required scope
|
|
57
|
+
*/
|
|
58
|
+
function hasScope(ctx, scope) {
|
|
59
|
+
return ctx.type === 'service' && ctx.scopes.includes(scope);
|
|
60
|
+
}
|
|
44
61
|
//# sourceMappingURL=AuthContext.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"AuthContext.js","sourceRoot":"","sources":["../../../src/api/auth/AuthContext.ts"],"names":[],"mappings":";AAAA;;;;;;;GAOG;;
|
|
1
|
+
{"version":3,"file":"AuthContext.js","sourceRoot":"","sources":["../../../src/api/auth/AuthContext.ts"],"names":[],"mappings":";AAAA;;;;;;;GAOG;;AA8BH,kCAEC;AAED,gCAEC;AAKD,4BAEC;AAKD,wCAEC;AAKD,oCAQC;AAED,8BAEC;AAED,sCAEC;AAKD,4BAEC;AAhDD,SAAgB,WAAW,CAAC,GAAgB;IAC1C,OAAO,GAAG,CAAC,IAAI,KAAK,OAAO,CAAC;AAC9B,CAAC;AAED,SAAgB,UAAU,CAAC,GAAgB;IACzC,OAAO,GAAG,CAAC,IAAI,KAAK,MAAM,CAAC;AAC7B,CAAC;AAED;;GAEG;AACH,SAAgB,QAAQ,CAAC,GAAgB;IACvC,OAAO,GAAG,CAAC,IAAI,KAAK,OAAO,CAAC,CAAC,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC,CAAC,SAAS,CAAC;AACtD,CAAC;AAED;;GAEG;AACH,SAAgB,cAAc,CAAC,GAAgB;IAC7C,OAAO,GAAG,CAAC,IAAI,KAAK,OAAO,CAAC,CAAC,CAAC,GAAG,CAAC,WAAW,CAAC,CAAC,CAAC,SAAS,CAAC;AAC5D,CAAC;AAED;;GAEG;AACH,SAAgB,YAAY,CAAC,GAAgB;IAC3C,IAAI,GAAG,CAAC,IAAI,KAAK,OAAO,EAAE,CAAC;QACzB,OAAO,GAAG,CAAC,SAAS,CAAC;IACvB,CAAC;IACD,IAAI,GAAG,CAAC,IAAI,KAAK,MAAM,EAAE,CAAC;QACxB,OAAO,GAAG,CAAC,SAAS,CAAC;IACvB,CAAC;IACD,OAAO,SAAS,CAAC;AACnB,CAAC;AAED,SAAgB,SAAS,CAAC,GAAgB;IACxC,OAAO,GAAG,CAAC,IAAI,KAAK,MAAM,CAAC,CAAC,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC,CAAC,SAAS,CAAC;AACtD,CAAC;AAED,SAAgB,aAAa,CAAC,GAAgB;IAC5C,OAAO,GAAG,CAAC,IAAI,KAAK,SAAS,CAAC;AAChC,CAAC;AAED;;GAEG;AACH,SAAgB,QAAQ,CAAC,GAAgB,EAAE,KAAa;IACtD,OAAO,GAAG,CAAC,IAAI,KAAK,SAAS,IAAI,GAAG,CAAC,MAAM,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC;AAC9D,CAAC","sourcesContent":["/**\n * Authentication context representing the authenticated caller\n * \n * Authenticated caller context.\n * - Solid Token: user provides Bearer or DPoP token\n * - API Key: third-party provides client_id, API Server exchanges for token\n * - Node Token: edge node provides node API key\n */\n\nexport interface SolidAuthContext {\n type: 'solid';\n webId: string;\n accountId?: string;\n clientId?: string;\n clientSecret?: string; // For client credentials auth\n displayName?: string;\n accessToken?: string;\n tokenType?: 'Bearer' | 'DPoP';\n /** Whether this was authenticated via API Key (client credentials) */\n viaApiKey?: boolean;\n}\n\nexport interface NodeAuthContext {\n type: 'node';\n nodeId: string;\n accountId?: string;\n}\n\nexport interface ServiceAuthContext {\n type: 'service';\n serviceType: 'local' | 'business' | 'cloud' | 'compute';\n serviceId: string;\n scopes: string[];\n}\n\nexport type AuthContext = SolidAuthContext | NodeAuthContext | ServiceAuthContext;\n\nexport function isSolidAuth(ctx: AuthContext): ctx is SolidAuthContext {\n return ctx.type === 'solid';\n}\n\nexport function isNodeAuth(ctx: AuthContext): ctx is NodeAuthContext {\n return ctx.type === 'node';\n}\n\n/**\n * Get webId from auth context\n */\nexport function getWebId(ctx: AuthContext): string | undefined {\n return ctx.type === 'solid' ? ctx.webId : undefined;\n}\n\n/**\n * Get display name from auth context\n */\nexport function getDisplayName(ctx: AuthContext): string | undefined {\n return ctx.type === 'solid' ? ctx.displayName : undefined;\n}\n\n/**\n * Get accountId from auth context (if available)\n */\nexport function getAccountId(ctx: AuthContext): string | undefined {\n if (ctx.type === 'solid') {\n return ctx.accountId;\n }\n if (ctx.type === 'node') {\n return ctx.accountId;\n }\n return undefined;\n}\n\nexport function getNodeId(ctx: AuthContext): string | undefined {\n return ctx.type === 'node' ? ctx.nodeId : undefined;\n}\n\nexport function isServiceAuth(ctx: AuthContext): ctx is ServiceAuthContext {\n return ctx.type === 'service';\n}\n\n/**\n * Check if a service auth context has the required scope\n */\nexport function hasScope(ctx: AuthContext, scope: string): boolean {\n return ctx.type === 'service' && ctx.scopes.includes(scope);\n}\n"]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ClientCredentialsAuthenticator.js","sourceRoot":"","sources":["../../../src/api/auth/ClientCredentialsAuthenticator.ts"],"names":[],"mappings":"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","sourcesContent":["import type { IncomingMessage } from 'node:http';\nimport { getLoggerFor } from 'global-logger-factory';\nimport type { Authenticator, AuthResult } from './Authenticator';\nimport type { SolidAuthContext } from './AuthContext';\n\n/**\n * Interface for token cache\n */\nexport interface TokenCache {\n get(clientId: string): Promise<{ token: string; webId: string; expiresAt: Date } | undefined>;\n set(clientId: string, token: string, webId: string, expiresAt: Date): Promise<void>;\n}\n\nexport interface ClientCredentialsAuthenticatorOptions {\n tokenCache?: TokenCache;\n /**\n * CSS token endpoint URL\n */\n tokenEndpoint: string;\n}\n\n/**\n * Authenticator for API Keys in sk-xxx format.\n * \n * Format: sk-base64(client_id:client_secret)\n * \n * This authenticator:\n * 1. Decodes the API Key to get client_id and client_secret\n * 2. Exchanges them for a Solid Token via CSS token endpoint\n * 3. Extracts webId from the token response\n * 4. Returns a SolidAuthContext\n */\nexport class ClientCredentialsAuthenticator implements Authenticator {\n private readonly logger = getLoggerFor(this);\n private readonly tokenCache?: TokenCache;\n private readonly tokenEndpoint: string;\n\n public constructor(options: ClientCredentialsAuthenticatorOptions) {\n this.tokenCache = options.tokenCache;\n this.tokenEndpoint = options.tokenEndpoint;\n }\n\n public canAuthenticate(request: IncomingMessage): boolean {\n const auth = request.headers.authorization;\n if (!auth?.startsWith('Bearer ')) {\n return false;\n }\n // If there's a DPoP header, it's a Solid Token, not an API Key\n if (request.headers.dpop) {\n return false;\n }\n const token = auth.slice(7).trim();\n if (!token) {\n return false;\n }\n // Only handle sk-xxx format or non-JWT tokens\n return token.startsWith('sk-') || !this.isJwt(token);\n }\n\n public async authenticate(request: IncomingMessage): Promise<AuthResult> {\n const authorization = request.headers.authorization;\n if (!authorization?.startsWith('Bearer ')) {\n return { success: false, error: 'Missing Bearer token' };\n }\n\n const token = authorization.slice(7).trim();\n if (!token) {\n return { success: false, error: 'Empty API Key' };\n }\n\n try {\n let clientId: string;\n let clientSecret: string;\n\n // Parse sk-xxx format (base64 encoded client_id:client_secret)\n if (token.startsWith('sk-')) {\n const base64 = token.slice(3);\n try {\n const decoded = Buffer.from(base64, 'base64').toString('utf-8');\n const colonIndex = decoded.indexOf(':');\n if (colonIndex === -1) {\n return { success: false, error: 'Invalid API Key format: missing colon separator' };\n }\n clientId = decoded.slice(0, colonIndex);\n clientSecret = decoded.slice(colonIndex + 1);\n \n if (!clientId || !clientSecret) {\n return { success: false, error: 'Invalid API Key format: empty client_id or client_secret' };\n }\n } catch {\n return { success: false, error: 'Invalid API Key encoding' };\n }\n } else {\n // Non sk- format not supported without database lookup\n return { success: false, error: 'Invalid API Key format: must start with sk-' };\n }\n\n // Check cache first\n if (this.tokenCache) {\n const cached = await this.tokenCache.get(clientId);\n if (cached && cached.expiresAt > new Date()) {\n this.logger.debug(`Using cached token for ${clientId.slice(0, 8)}...`);\n return {\n success: true,\n context: {\n type: 'solid',\n webId: cached.webId,\n accountId: cached.webId,\n clientId,\n clientSecret,\n viaApiKey: true,\n },\n };\n }\n }\n\n // Exchange for token at CSS endpoint\n console.log(`[ClientCredentialsAuthenticator] Exchanging credentials at ${this.tokenEndpoint}`);\n const tokenResult = await this.exchangeForToken(clientId, clientSecret);\n console.log(`[ClientCredentialsAuthenticator] Token exchange result: success=${tokenResult.success}, webId=${tokenResult.webId}, error=${tokenResult.error}`);\n \n if (!tokenResult.success || !tokenResult.webId) {\n return { success: false, error: tokenResult.error || 'Token exchange failed' };\n }\n\n // Cache the token\n if (this.tokenCache && tokenResult.expiresAt) {\n await this.tokenCache.set(clientId, tokenResult.token!, tokenResult.webId, tokenResult.expiresAt);\n }\n\n const context: SolidAuthContext = {\n type: 'solid',\n webId: tokenResult.webId,\n accountId: tokenResult.webId,\n clientId,\n clientSecret,\n viaApiKey: true,\n };\n\n this.logger.debug(`Authenticated API Key for webId: ${tokenResult.webId}`);\n return { success: true, context };\n } catch (error) {\n console.error(`[ClientCredentialsAuthenticator] API Key authentication error:`, error);\n this.logger.error(`API Key authentication error: ${error}`);\n return { success: false, error: 'Authentication failed' };\n }\n }\n\n private async exchangeForToken(clientId: string, clientSecret: string): Promise<{\n success: boolean;\n token?: string;\n webId?: string;\n expiresAt?: Date;\n error?: string;\n }> {\n // 开发模式:跳过 CSS token exchange\n if (process.env.NODE_ENV === 'development') {\n this.logger.warn(`[DEV] Skipping token exchange for ${clientId.slice(0, 8)}...`);\n return {\n success: true,\n token: `dev-token-${clientId}`,\n expiresAt: new Date(Date.now() + 3600000),\n };\n }\n\n try {\n const response = await fetch(this.tokenEndpoint, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/x-www-form-urlencoded',\n },\n body: new URLSearchParams({\n grant_type: 'client_credentials',\n client_id: clientId,\n client_secret: clientSecret,\n }),\n });\n\n if (!response.ok) {\n const error = await response.text();\n this.logger.warn(`Token exchange failed: ${response.status} ${error}`);\n return { success: false, error: `Token exchange failed: ${response.status}` };\n }\n\n const data = await response.json() as {\n access_token: string;\n expires_in?: number;\n token_type: string;\n webid?: string; // CSS returns webid in response\n };\n\n // Extract webId from token response or decode from JWT\n let webId = data.webid;\n if (!webId && data.access_token) {\n webId = this.extractWebIdFromJwt(data.access_token);\n }\n\n if (!webId) {\n return { success: false, error: 'Could not determine webId from token response' };\n }\n\n const expiresAt = data.expires_in\n ? new Date(Date.now() + data.expires_in * 1000 - 60000) // 1 min buffer\n : new Date(Date.now() + 3600000); // Default 1 hour\n\n return {\n success: true,\n token: data.access_token,\n webId,\n expiresAt,\n };\n } catch (error) {\n this.logger.error(`Token exchange error: ${error}`);\n return { success: false, error: 'Token exchange failed' };\n }\n }\n\n /**\n * Extract webId from JWT access token\n */\n private extractWebIdFromJwt(jwt: string): string | undefined {\n try {\n const parts = jwt.split('.');\n if (parts.length !== 3) {\n return undefined;\n }\n const payload = JSON.parse(Buffer.from(parts[1], 'base64').toString('utf-8'));\n return payload.webid || payload.webId || payload.sub;\n } catch {\n return undefined;\n }\n }\n\n private isJwt(token: string): boolean {\n return /^[A-Za-z0-9_-]+\\.[A-Za-z0-9_-]+\\.[A-Za-z0-9_-]+$/.test(token);\n }\n}\n\n// Re-export for backwards compatibility (these are no longer needed but keep for other code)\nexport interface ClientCredentialsRecord {\n clientId: string;\n clientSecret: string;\n webId: string;\n accountId: string;\n displayName?: string;\n createdAt: Date;\n}\n\nexport interface ClientCredentialsStore {\n findByClientId(clientId: string): Promise<ClientCredentialsRecord | undefined>;\n}\n"]}
|
|
1
|
+
{"version":3,"file":"ClientCredentialsAuthenticator.js","sourceRoot":"","sources":["../../../src/api/auth/ClientCredentialsAuthenticator.ts"],"names":[],"mappings":"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","sourcesContent":["import type { IncomingMessage } from 'node:http';\nimport { getLoggerFor } from 'global-logger-factory';\nimport type { Authenticator, AuthResult } from './Authenticator';\nimport type { SolidAuthContext } from './AuthContext';\n\n/**\n * Interface for token cache\n */\nexport interface TokenCache {\n get(clientId: string): Promise<{ token: string; webId: string; expiresAt: Date } | undefined>;\n set(clientId: string, token: string, webId: string, expiresAt: Date): Promise<void>;\n}\n\nexport interface ClientCredentialsAuthenticatorOptions {\n tokenCache?: TokenCache;\n /**\n * CSS token endpoint URL\n */\n tokenEndpoint: string;\n}\n\n/**\n * Authenticator for API Keys in sk-xxx format.\n * \n * Format: sk-base64(client_id:client_secret)\n * \n * This authenticator:\n * 1. Decodes the API Key to get client_id and client_secret\n * 2. Exchanges them for a Solid Token via CSS token endpoint\n * 3. Extracts webId from the token response\n * 4. Returns a SolidAuthContext\n */\nexport class ClientCredentialsAuthenticator implements Authenticator {\n private readonly logger = getLoggerFor(this);\n private readonly tokenCache?: TokenCache;\n private readonly tokenEndpoint: string;\n\n public constructor(options: ClientCredentialsAuthenticatorOptions) {\n this.tokenCache = options.tokenCache;\n this.tokenEndpoint = options.tokenEndpoint;\n }\n\n public canAuthenticate(request: IncomingMessage): boolean {\n const auth = request.headers.authorization;\n if (!auth?.startsWith('Bearer ')) {\n return false;\n }\n // If there's a DPoP header, it's a Solid Token, not an API Key\n if (request.headers.dpop) {\n return false;\n }\n const token = auth.slice(7).trim();\n if (!token) {\n return false;\n }\n // Only handle sk-xxx format or non-JWT tokens\n return token.startsWith('sk-') || !this.isJwt(token);\n }\n\n public async authenticate(request: IncomingMessage): Promise<AuthResult> {\n const authorization = request.headers.authorization;\n if (!authorization?.startsWith('Bearer ')) {\n return { success: false, error: 'Missing Bearer token' };\n }\n\n const token = authorization.slice(7).trim();\n if (!token) {\n return { success: false, error: 'Empty API Key' };\n }\n\n try {\n let clientId: string;\n let clientSecret: string;\n\n // Parse sk-xxx format (base64 encoded client_id:client_secret)\n if (token.startsWith('sk-')) {\n const base64 = token.slice(3);\n try {\n const decoded = Buffer.from(base64, 'base64').toString('utf-8');\n const colonIndex = decoded.indexOf(':');\n if (colonIndex === -1) {\n return { success: false, error: 'Invalid API Key format: missing colon separator' };\n }\n clientId = decoded.slice(0, colonIndex);\n clientSecret = decoded.slice(colonIndex + 1);\n \n if (!clientId || !clientSecret) {\n return { success: false, error: 'Invalid API Key format: empty client_id or client_secret' };\n }\n } catch {\n return { success: false, error: 'Invalid API Key encoding' };\n }\n } else {\n // Non sk- format not supported without database lookup\n return { success: false, error: 'Invalid API Key format: must start with sk-' };\n }\n\n // Check cache first\n if (this.tokenCache) {\n const cached = await this.tokenCache.get(clientId);\n if (cached && cached.expiresAt > new Date()) {\n this.logger.debug(`Using cached token for ${clientId.slice(0, 8)}...`);\n return {\n success: true,\n context: {\n type: 'solid',\n webId: cached.webId,\n accountId: cached.webId,\n clientId,\n clientSecret,\n viaApiKey: true,\n },\n };\n }\n }\n\n // Exchange for token at CSS endpoint\n console.log(`[ClientCredentialsAuthenticator] Exchanging credentials at ${this.tokenEndpoint}`);\n const tokenResult = await this.exchangeForToken(clientId, clientSecret);\n console.log(`[ClientCredentialsAuthenticator] Token exchange result: success=${tokenResult.success}, webId=${tokenResult.webId}, error=${tokenResult.error}`);\n \n if (!tokenResult.success || !tokenResult.webId) {\n return { success: false, error: tokenResult.error || 'Token exchange failed' };\n }\n\n // Cache the token\n if (this.tokenCache && tokenResult.expiresAt) {\n await this.tokenCache.set(clientId, tokenResult.token!, tokenResult.webId, tokenResult.expiresAt);\n }\n\n const context: SolidAuthContext = {\n type: 'solid',\n webId: tokenResult.webId,\n accountId: tokenResult.webId,\n clientId,\n clientSecret,\n viaApiKey: true,\n };\n\n this.logger.debug(`Authenticated API Key for webId: ${tokenResult.webId}`);\n return { success: true, context };\n } catch (error) {\n console.error(`[ClientCredentialsAuthenticator] API Key authentication error:`, error);\n this.logger.error(`API Key authentication error: ${error}`);\n return { success: false, error: 'Authentication failed' };\n }\n }\n\n private async exchangeForToken(clientId: string, clientSecret: string): Promise<{\n success: boolean;\n token?: string;\n webId?: string;\n expiresAt?: Date;\n error?: string;\n }> {\n // 开发模式:跳过 CSS token exchange\n if (process.env.NODE_ENV === 'development') {\n this.logger.warn(`[DEV] Skipping token exchange for ${clientId.slice(0, 8)}...`);\n return {\n success: true,\n token: `dev-token-${clientId}`,\n expiresAt: new Date(Date.now() + 3600000),\n };\n }\n\n try {\n const response = await fetch(this.tokenEndpoint, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/x-www-form-urlencoded',\n },\n body: new URLSearchParams({\n grant_type: 'client_credentials',\n client_id: clientId,\n client_secret: clientSecret,\n }),\n });\n\n if (!response.ok) {\n const error = await response.text();\n this.logger.warn(`Token exchange failed: ${response.status} ${error}`);\n return { success: false, error: `Token exchange failed: ${response.status}` };\n }\n\n const data = await response.json() as {\n access_token: string;\n expires_in?: number;\n token_type: string;\n webid?: string; // CSS returns webid in response\n };\n\n // Extract webId from token response or decode from JWT\n let webId = data.webid;\n if (!webId && data.access_token) {\n webId = this.extractWebIdFromJwt(data.access_token);\n }\n\n if (!webId) {\n return { success: false, error: 'Could not determine webId from token response' };\n }\n\n const expiresAt = data.expires_in\n ? new Date(Date.now() + data.expires_in * 1000 - 60000) // 1 min buffer\n : new Date(Date.now() + 3600000); // Default 1 hour\n\n return {\n success: true,\n token: data.access_token,\n webId,\n expiresAt,\n };\n } catch (error) {\n this.logger.error(`Token exchange error: ${error}`);\n return { success: false, error: 'Token exchange failed' };\n }\n }\n\n /**\n * Extract webId from JWT access token\n */\n private extractWebIdFromJwt(jwt: string): string | undefined {\n try {\n const parts = jwt.split('.');\n if (parts.length !== 3) {\n return undefined;\n }\n const payload = JSON.parse(Buffer.from(parts[1], 'base64').toString('utf-8'));\n return payload.webid || payload.webId || payload.sub;\n } catch {\n return undefined;\n }\n }\n\n private isJwt(token: string): boolean {\n return /^[A-Za-z0-9_-]+\\.[A-Za-z0-9_-]+\\.[A-Za-z0-9_-]+$/.test(token);\n }\n}\n\n// Re-export for backwards compatibility (these are no longer needed but keep for other code)\nexport interface ClientCredentialsRecord {\n clientId: string;\n webId: string;\n accountId: string;\n displayName?: string;\n createdAt: Date;\n}\n\nexport interface ClientCredentialsStore {\n findByClientId(clientId: string): Promise<ClientCredentialsRecord | undefined>;\n}\n"]}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { IncomingMessage } from 'node:http';
|
|
2
|
+
import type { Authenticator, AuthResult } from './Authenticator';
|
|
3
|
+
import type { ServiceTokenRepository } from '../../identity/drizzle/ServiceTokenRepository';
|
|
4
|
+
export interface ServiceTokenAuthenticatorOptions {
|
|
5
|
+
repository: ServiceTokenRepository;
|
|
6
|
+
}
|
|
7
|
+
/**
|
|
8
|
+
* Authenticator for service tokens (Business, Local SP, Cloud, Compute).
|
|
9
|
+
*
|
|
10
|
+
* Format: Bearer svc-xxx
|
|
11
|
+
*/
|
|
12
|
+
export declare class ServiceTokenAuthenticator implements Authenticator {
|
|
13
|
+
private readonly logger;
|
|
14
|
+
private readonly repo;
|
|
15
|
+
constructor(options: ServiceTokenAuthenticatorOptions);
|
|
16
|
+
canAuthenticate(request: IncomingMessage): boolean;
|
|
17
|
+
authenticate(request: IncomingMessage): Promise<AuthResult>;
|
|
18
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.ServiceTokenAuthenticator = void 0;
|
|
4
|
+
const global_logger_factory_1 = require("global-logger-factory");
|
|
5
|
+
/**
|
|
6
|
+
* Authenticator for service tokens (Business, Local SP, Cloud, Compute).
|
|
7
|
+
*
|
|
8
|
+
* Format: Bearer svc-xxx
|
|
9
|
+
*/
|
|
10
|
+
class ServiceTokenAuthenticator {
|
|
11
|
+
constructor(options) {
|
|
12
|
+
this.logger = (0, global_logger_factory_1.getLoggerFor)(this);
|
|
13
|
+
this.repo = options.repository;
|
|
14
|
+
}
|
|
15
|
+
canAuthenticate(request) {
|
|
16
|
+
const auth = request.headers.authorization;
|
|
17
|
+
if (!auth?.startsWith('Bearer ')) {
|
|
18
|
+
return false;
|
|
19
|
+
}
|
|
20
|
+
const token = auth.slice(7).trim();
|
|
21
|
+
// Service tokens start with 'svc-'
|
|
22
|
+
return token.startsWith('svc-');
|
|
23
|
+
}
|
|
24
|
+
async authenticate(request) {
|
|
25
|
+
const auth = request.headers.authorization;
|
|
26
|
+
const token = auth.slice(7).trim();
|
|
27
|
+
try {
|
|
28
|
+
const record = await this.repo.verifyToken(token);
|
|
29
|
+
if (!record) {
|
|
30
|
+
return { success: false, error: 'Invalid service token' };
|
|
31
|
+
}
|
|
32
|
+
this.logger.debug(`Authenticated service: ${record.serviceType}:${record.serviceId}`);
|
|
33
|
+
return {
|
|
34
|
+
success: true,
|
|
35
|
+
context: {
|
|
36
|
+
type: 'service',
|
|
37
|
+
serviceType: record.serviceType,
|
|
38
|
+
serviceId: record.serviceId,
|
|
39
|
+
scopes: record.scopes,
|
|
40
|
+
},
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
catch (error) {
|
|
44
|
+
this.logger.error(`Service token authentication failed: ${error}`);
|
|
45
|
+
return { success: false, error: 'Internal authentication error' };
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
exports.ServiceTokenAuthenticator = ServiceTokenAuthenticator;
|
|
50
|
+
//# sourceMappingURL=ServiceTokenAuthenticator.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ServiceTokenAuthenticator.js","sourceRoot":"","sources":["../../../src/api/auth/ServiceTokenAuthenticator.ts"],"names":[],"mappings":";;;AACA,iEAAqD;AAQrD;;;;GAIG;AACH,MAAa,yBAAyB;IAIpC,YAAmB,OAAyC;QAH3C,WAAM,GAAG,IAAA,oCAAY,EAAC,IAAI,CAAC,CAAC;QAI3C,IAAI,CAAC,IAAI,GAAG,OAAO,CAAC,UAAU,CAAC;IACjC,CAAC;IAEM,eAAe,CAAC,OAAwB;QAC7C,MAAM,IAAI,GAAG,OAAO,CAAC,OAAO,CAAC,aAAa,CAAC;QAC3C,IAAI,CAAC,IAAI,EAAE,UAAU,CAAC,SAAS,CAAC,EAAE,CAAC;YACjC,OAAO,KAAK,CAAC;QACf,CAAC;QACD,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;QACnC,mCAAmC;QACnC,OAAO,KAAK,CAAC,UAAU,CAAC,MAAM,CAAC,CAAC;IAClC,CAAC;IAEM,KAAK,CAAC,YAAY,CAAC,OAAwB;QAChD,MAAM,IAAI,GAAG,OAAO,CAAC,OAAO,CAAC,aAAc,CAAC;QAC5C,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;QAEnC,IAAI,CAAC;YACH,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,IAAI,CAAC,WAAW,CAAC,KAAK,CAAC,CAAC;YAClD,IAAI,CAAC,MAAM,EAAE,CAAC;gBACZ,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,KAAK,EAAE,uBAAuB,EAAE,CAAC;YAC5D,CAAC;YAED,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,0BAA0B,MAAM,CAAC,WAAW,IAAI,MAAM,CAAC,SAAS,EAAE,CAAC,CAAC;YAEtF,OAAO;gBACL,OAAO,EAAE,IAAI;gBACb,OAAO,EAAE;oBACP,IAAI,EAAE,SAAS;oBACf,WAAW,EAAE,MAAM,CAAC,WAAW;oBAC/B,SAAS,EAAE,MAAM,CAAC,SAAS;oBAC3B,MAAM,EAAE,MAAM,CAAC,MAAM;iBACtB;aACF,CAAC;QACJ,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,wCAAwC,KAAK,EAAE,CAAC,CAAC;YACnE,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,KAAK,EAAE,+BAA+B,EAAE,CAAC;QACpE,CAAC;IACH,CAAC;CACF;AA5CD,8DA4CC","sourcesContent":["import type { IncomingMessage } from 'node:http';\nimport { getLoggerFor } from 'global-logger-factory';\nimport type { Authenticator, AuthResult } from './Authenticator';\nimport type { ServiceTokenRepository } from '../../identity/drizzle/ServiceTokenRepository';\n\nexport interface ServiceTokenAuthenticatorOptions {\n repository: ServiceTokenRepository;\n}\n\n/**\n * Authenticator for service tokens (Business, Local SP, Cloud, Compute).\n *\n * Format: Bearer svc-xxx\n */\nexport class ServiceTokenAuthenticator implements Authenticator {\n private readonly logger = getLoggerFor(this);\n private readonly repo: ServiceTokenRepository;\n\n public constructor(options: ServiceTokenAuthenticatorOptions) {\n this.repo = options.repository;\n }\n\n public canAuthenticate(request: IncomingMessage): boolean {\n const auth = request.headers.authorization;\n if (!auth?.startsWith('Bearer ')) {\n return false;\n }\n const token = auth.slice(7).trim();\n // Service tokens start with 'svc-'\n return token.startsWith('svc-');\n }\n\n public async authenticate(request: IncomingMessage): Promise<AuthResult> {\n const auth = request.headers.authorization!;\n const token = auth.slice(7).trim();\n\n try {\n const record = await this.repo.verifyToken(token);\n if (!record) {\n return { success: false, error: 'Invalid service token' };\n }\n\n this.logger.debug(`Authenticated service: ${record.serviceType}:${record.serviceId}`);\n\n return {\n success: true,\n context: {\n type: 'service',\n serviceType: record.serviceType,\n serviceId: record.serviceId,\n scopes: record.scopes,\n },\n };\n } catch (error) {\n this.logger.error(`Service token authentication failed: ${error}`);\n return { success: false, error: 'Internal authentication error' };\n }\n }\n}\n"]}
|
package/dist/api/auth/index.d.ts
CHANGED
package/dist/api/auth/index.js
CHANGED
|
@@ -18,6 +18,7 @@ __exportStar(require("./Authenticator"), exports);
|
|
|
18
18
|
__exportStar(require("./AuthContext"), exports);
|
|
19
19
|
__exportStar(require("./SolidTokenAuthenticator"), exports);
|
|
20
20
|
__exportStar(require("./ClientCredentialsAuthenticator"), exports);
|
|
21
|
+
__exportStar(require("./ServiceTokenAuthenticator"), exports);
|
|
21
22
|
// export * from './NodeTokenAuthenticator'; // TODO: Fix later
|
|
22
23
|
__exportStar(require("./MultiAuthenticator"), exports);
|
|
23
24
|
//# sourceMappingURL=index.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/api/auth/index.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;AAAA,kDAAgC;AAChC,gDAA8B;AAC9B,4DAA0C;AAC1C,mEAAiD;AACjD,gEAAgE;AAChE,uDAAqC","sourcesContent":["export * from './Authenticator';\nexport * from './AuthContext';\nexport * from './SolidTokenAuthenticator';\nexport * from './ClientCredentialsAuthenticator';\n// export * from './NodeTokenAuthenticator'; // TODO: Fix later\nexport * from './MultiAuthenticator';\n"]}
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/api/auth/index.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;AAAA,kDAAgC;AAChC,gDAA8B;AAC9B,4DAA0C;AAC1C,mEAAiD;AACjD,8DAA4C;AAC5C,gEAAgE;AAChE,uDAAqC","sourcesContent":["export * from './Authenticator';\nexport * from './AuthContext';\nexport * from './SolidTokenAuthenticator';\nexport * from './ClientCredentialsAuthenticator';\nexport * from './ServiceTokenAuthenticator';\n// export * from './NodeTokenAuthenticator'; // TODO: Fix later\nexport * from './MultiAuthenticator';\n"]}
|
|
@@ -41,14 +41,4 @@ export declare class VercelAiProvider implements AiProvider {
|
|
|
41
41
|
private handleRateLimitError;
|
|
42
42
|
private getProviderConfig;
|
|
43
43
|
private createProvider;
|
|
44
|
-
private getPodBaseUrlFromWebId;
|
|
45
|
-
private getDefaultBaseUrl;
|
|
46
|
-
/**
|
|
47
|
-
* 使用 Default Agent 流式响应
|
|
48
|
-
*/
|
|
49
|
-
private streamWithDefaultAgent;
|
|
50
|
-
/**
|
|
51
|
-
* 从上下文获取 Solid Token
|
|
52
|
-
*/
|
|
53
|
-
private getSolidToken;
|
|
54
44
|
}
|
|
@@ -13,7 +13,7 @@ const global_logger_factory_1 = require("global-logger-factory");
|
|
|
13
13
|
const undici_1 = require("undici");
|
|
14
14
|
const AuthContext_1 = require("../auth/AuthContext");
|
|
15
15
|
const types_1 = require("../../credential/schema/types");
|
|
16
|
-
const
|
|
16
|
+
const provider_registry_1 = require("../service/provider-registry");
|
|
17
17
|
// Create a proxy-aware fetch function
|
|
18
18
|
function createProxyFetch(proxyUrl) {
|
|
19
19
|
const agent = new undici_1.ProxyAgent(proxyUrl);
|
|
@@ -40,11 +40,8 @@ class VercelAiProvider {
|
|
|
40
40
|
const userId = auth ? ((0, AuthContext_1.getWebId)(auth) ?? (0, AuthContext_1.getAccountId)(auth) ?? 'anonymous') : 'anonymous';
|
|
41
41
|
// 从 Pod 获取配置
|
|
42
42
|
const config = await this.getProviderConfig(context);
|
|
43
|
-
// 无有效配置,降级到 Default Agent
|
|
44
43
|
if (!config) {
|
|
45
|
-
|
|
46
|
-
yield* this.streamWithDefaultAgent(messages, context);
|
|
47
|
-
return;
|
|
44
|
+
throw new Error('No AI provider configured. Please configure Pod AI provider or set DEFAULT_API_BASE.');
|
|
48
45
|
}
|
|
49
46
|
const model = options?.model ?? config.defaultModel ?? process.env.DEFAULT_MODEL ?? 'stepfun/step-3.5-flash:free';
|
|
50
47
|
this.logger.debug(`Streaming response for ${userId}, model: ${model}`);
|
|
@@ -141,38 +138,25 @@ Model: ${model}
|
|
|
141
138
|
config = undefined;
|
|
142
139
|
}
|
|
143
140
|
}
|
|
144
|
-
// 用户 Pod 有配置,优先使用
|
|
141
|
+
// 1. 用户 Pod 有配置,优先使用
|
|
145
142
|
if (config?.apiKey) {
|
|
146
143
|
return {
|
|
147
|
-
baseURL: config.baseUrl ||
|
|
144
|
+
baseURL: config.baseUrl || (0, provider_registry_1.getDefaultBaseUrl)(),
|
|
148
145
|
apiKey: config.apiKey,
|
|
149
146
|
proxy: config.proxyUrl,
|
|
150
147
|
credentialId: config.credentialId,
|
|
151
148
|
};
|
|
152
149
|
}
|
|
153
|
-
//
|
|
154
|
-
|
|
150
|
+
// 2. 平台 Provider
|
|
151
|
+
const platformBase = process.env.DEFAULT_API_BASE;
|
|
152
|
+
if (platformBase) {
|
|
155
153
|
return {
|
|
156
|
-
baseURL:
|
|
157
|
-
apiKey: process.env.
|
|
154
|
+
baseURL: platformBase,
|
|
155
|
+
apiKey: process.env.DEFAULT_API_KEY || '',
|
|
158
156
|
};
|
|
159
157
|
}
|
|
160
|
-
//
|
|
161
|
-
|
|
162
|
-
return {
|
|
163
|
-
baseURL: 'https://generativelanguage.googleapis.com/v1beta/openai',
|
|
164
|
-
apiKey: process.env.GOOGLE_API_KEY,
|
|
165
|
-
};
|
|
166
|
-
}
|
|
167
|
-
// OpenRouter API Key
|
|
168
|
-
if (process.env.OPENROUTER_API_KEY) {
|
|
169
|
-
return {
|
|
170
|
-
baseURL: 'https://openrouter.ai/api/v1',
|
|
171
|
-
apiKey: process.env.OPENROUTER_API_KEY,
|
|
172
|
-
};
|
|
173
|
-
}
|
|
174
|
-
// 无有效配置,返回 null 表示需要降级到 Default Agent
|
|
175
|
-
this.logger.debug('No valid AI config found, will use Default Agent');
|
|
158
|
+
// 3. 无配置
|
|
159
|
+
this.logger.debug('No valid AI config found');
|
|
176
160
|
return null;
|
|
177
161
|
}
|
|
178
162
|
createProvider(config) {
|
|
@@ -184,99 +168,6 @@ Model: ${model}
|
|
|
184
168
|
}
|
|
185
169
|
return (0, openai_1.createOpenAI)(options);
|
|
186
170
|
}
|
|
187
|
-
getPodBaseUrlFromWebId(webId) {
|
|
188
|
-
try {
|
|
189
|
-
const url = new URL(webId);
|
|
190
|
-
url.hash = ''; // 清除 fragment
|
|
191
|
-
const pathParts = url.pathname.split('/');
|
|
192
|
-
if (pathParts.includes('profile')) {
|
|
193
|
-
const profileIndex = pathParts.indexOf('profile');
|
|
194
|
-
url.pathname = pathParts.slice(0, profileIndex).join('/');
|
|
195
|
-
}
|
|
196
|
-
return url.toString().replace(/\/$/, '') + '/';
|
|
197
|
-
}
|
|
198
|
-
catch {
|
|
199
|
-
return '';
|
|
200
|
-
}
|
|
201
|
-
}
|
|
202
|
-
getDefaultBaseUrl(provider) {
|
|
203
|
-
const urls = {
|
|
204
|
-
openai: 'https://api.openai.com/v1',
|
|
205
|
-
google: 'https://generativelanguage.googleapis.com/v1beta/openai',
|
|
206
|
-
anthropic: 'https://api.anthropic.com/v1',
|
|
207
|
-
deepseek: 'https://api.deepseek.com/v1',
|
|
208
|
-
openrouter: 'https://openrouter.ai/api/v1',
|
|
209
|
-
ollama: 'http://localhost:11434/v1',
|
|
210
|
-
mistral: 'https://api.mistral.ai/v1',
|
|
211
|
-
cohere: 'https://api.cohere.ai/v1',
|
|
212
|
-
zhipu: 'https://open.bigmodel.cn/api/paas/v4',
|
|
213
|
-
};
|
|
214
|
-
return urls[provider.toLowerCase()] || urls.openrouter;
|
|
215
|
-
}
|
|
216
|
-
/**
|
|
217
|
-
* 使用 Default Agent 流式响应
|
|
218
|
-
*/
|
|
219
|
-
async *streamWithDefaultAgent(messages, context) {
|
|
220
|
-
// 检查 Default Agent 是否可用
|
|
221
|
-
if (!(0, default_agent_1.isDefaultAgentAvailable)()) {
|
|
222
|
-
yield '抱歉,您还没有配置 AI 服务,且系统默认 AI 也未配置。请先配置您的 AI API Key。';
|
|
223
|
-
return;
|
|
224
|
-
}
|
|
225
|
-
// 构建 Default Agent 上下文
|
|
226
|
-
const auth = context?.auth;
|
|
227
|
-
const webId = auth ? (0, AuthContext_1.getWebId)(auth) : undefined;
|
|
228
|
-
if (!webId) {
|
|
229
|
-
yield '抱歉,无法获取您的身份信息,请先登录。';
|
|
230
|
-
return;
|
|
231
|
-
}
|
|
232
|
-
const podBaseUrl = this.getPodBaseUrlFromWebId(webId);
|
|
233
|
-
const solidToken = this.getSolidToken(context);
|
|
234
|
-
if (!solidToken) {
|
|
235
|
-
yield '抱歉,无法获取访问令牌,请重新登录。';
|
|
236
|
-
return;
|
|
237
|
-
}
|
|
238
|
-
const agentContext = {
|
|
239
|
-
solidToken,
|
|
240
|
-
podBaseUrl,
|
|
241
|
-
webId,
|
|
242
|
-
};
|
|
243
|
-
// 获取最后一条用户消息
|
|
244
|
-
const lastUserMessage = messages.filter(m => m.role === 'user').pop()?.content || '';
|
|
245
|
-
try {
|
|
246
|
-
yield* (0, default_agent_1.streamDefaultAgent)(lastUserMessage, agentContext);
|
|
247
|
-
}
|
|
248
|
-
catch (error) {
|
|
249
|
-
this.logger.error(`Default Agent error: ${error}`);
|
|
250
|
-
yield `抱歉,Default Agent 出现错误:${error instanceof Error ? error.message : String(error)}`;
|
|
251
|
-
}
|
|
252
|
-
}
|
|
253
|
-
/**
|
|
254
|
-
* 从上下文获取 Solid Token
|
|
255
|
-
*/
|
|
256
|
-
getSolidToken(context) {
|
|
257
|
-
// 尝试从 context 中获取 token
|
|
258
|
-
// 这里需要根据实际的 auth 实现来获取
|
|
259
|
-
const auth = context?.auth;
|
|
260
|
-
if (!auth)
|
|
261
|
-
return undefined;
|
|
262
|
-
// 如果 auth 中有 token,直接返回
|
|
263
|
-
if ('token' in auth && typeof auth.token === 'string') {
|
|
264
|
-
return auth.token;
|
|
265
|
-
}
|
|
266
|
-
// 如果有 accessToken
|
|
267
|
-
if ('accessToken' in auth && typeof auth.accessToken === 'string') {
|
|
268
|
-
return auth.accessToken;
|
|
269
|
-
}
|
|
270
|
-
// 尝试从 credentials 获取
|
|
271
|
-
if ('credentials' in auth && auth.credentials) {
|
|
272
|
-
const creds = auth.credentials;
|
|
273
|
-
if (creds.accessToken)
|
|
274
|
-
return creds.accessToken;
|
|
275
|
-
if (creds.token)
|
|
276
|
-
return creds.token;
|
|
277
|
-
}
|
|
278
|
-
return undefined;
|
|
279
|
-
}
|
|
280
171
|
}
|
|
281
172
|
exports.VercelAiProvider = VercelAiProvider;
|
|
282
173
|
//# sourceMappingURL=ai-provider.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ai-provider.js","sourceRoot":"","sources":["../../../src/api/chatkit/ai-provider.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * AI Provider Adapter\n *\n * Adapts existing AI services to the ChatKit AiProvider interface.\n * Includes 429 rate limit handling with credential status backfill.\n */\n\nimport { createOpenAI } from '@ai-sdk/openai';\nimport { streamText, APICallError } from 'ai';\nimport { getLoggerFor } from 'global-logger-factory';\nimport { ProxyAgent } from 'undici';\nimport type { AiProvider } from './service';\nimport type { StoreContext } from './store';\nimport type { PodChatKitStore } from './pod-store';\nimport type { AuthContext } from '../auth/AuthContext';\nimport { getWebId, getAccountId } from '../auth/AuthContext';\nimport { CredentialStatus } from '../../credential/schema/types';\nimport { isDefaultAgentAvailable, streamDefaultAgent, type DefaultAgentContext } from './default-agent';\n\n// Create a proxy-aware fetch function\nfunction createProxyFetch(proxyUrl: string): typeof fetch {\n const agent = new ProxyAgent(proxyUrl);\n return (url, init) => fetch(url, { ...init, dispatcher: agent } as any);\n}\n\nexport interface VercelAiProviderOptions {\n store: PodChatKitStore;\n}\n\n/**\n * Vercel AI SDK based provider\n *\n * Uses PodChatKitStore to get AI provider settings from Pod.\n * Reuses the same Session cached in StoreContext.\n */\nexport class VercelAiProvider implements AiProvider {\n private readonly logger = getLoggerFor(this);\n private readonly store: PodChatKitStore;\n\n public constructor(options: VercelAiProviderOptions) {\n this.store = options.store;\n }\n\n /**\n * Stream a response for the given messages\n */\n public async *streamResponse(\n messages: Array<{ role: 'system' | 'user' | 'assistant'; content: string }>,\n options?: {\n model?: string;\n temperature?: number;\n maxTokens?: number;\n context?: unknown;\n },\n ): AsyncIterable<string> {\n // Get context (contains auth and cached session)\n const context = options?.context as StoreContext | undefined;\n const auth = context?.auth as AuthContext | undefined;\n const userId = auth ? (getWebId(auth) ?? getAccountId(auth) ?? 'anonymous') : 'anonymous';\n\n // 从 Pod 获取配置\n const config = await this.getProviderConfig(context);\n\n // 无有效配置,降级到 Default Agent\n if (!config) {\n this.logger.info(`No valid AI config for ${userId}, falling back to Default Agent`);\n yield* this.streamWithDefaultAgent(messages, context);\n return;\n }\n\n const model = options?.model ?? config.defaultModel ?? process.env.DEFAULT_MODEL ?? 'stepfun/step-3.5-flash:free';\n\n this.logger.debug(`Streaming response for ${userId}, model: ${model}`);\n\n const provider = this.createProvider(config);\n\n try {\n const result = streamText({\n model: provider.chat(model),\n messages: messages as any,\n temperature: options?.temperature,\n maxTokens: options?.maxTokens,\n system: `You are a helpful AI assistant running on Xpod (a Solid Pod-based platform).\n\nYour capabilities:\n1. Help users with various tasks\n2. Use user's configured provider/model to respond consistently\n\nCurrent AI Provider: ${config.baseURL.includes('openrouter') ? 'OpenRouter (Free)' : 'Custom'}\nModel: ${model}\n`,\n } as any);\n\n // Stream text chunks\n for await (const chunk of result.textStream) {\n yield chunk;\n }\n } catch (error) {\n // Handle 429 rate limit errors\n if (this.isRateLimitError(error)) {\n await this.handleRateLimitError(error, context, config.credentialId);\n }\n throw error;\n }\n }\n\n /**\n * Check if error is a 429 rate limit error\n */\n private isRateLimitError(error: unknown): boolean {\n if (error instanceof APICallError) {\n return error.statusCode === 429;\n }\n if (error && typeof error === 'object') {\n const err = error as any;\n return err.status === 429 || err.statusCode === 429 || err.code === 'rate_limit_exceeded';\n }\n return false;\n }\n\n /**\n * Handle 429 rate limit error by updating credential status\n */\n private async handleRateLimitError(\n error: unknown,\n context: StoreContext | undefined,\n credentialId: string | undefined,\n ): Promise<void> {\n if (!context || !credentialId) {\n this.logger.debug('Cannot update credential status: missing context or credentialId');\n return;\n }\n\n let rateLimitResetAt: Date | undefined;\n if (error instanceof APICallError && error.responseHeaders) {\n const retryAfter = error.responseHeaders['retry-after'];\n if (retryAfter) {\n const seconds = parseInt(retryAfter, 10);\n if (!isNaN(seconds)) {\n rateLimitResetAt = new Date(Date.now() + seconds * 1000);\n } else {\n const date = new Date(retryAfter);\n if (!isNaN(date.getTime())) {\n rateLimitResetAt = date;\n }\n }\n }\n }\n\n if (!rateLimitResetAt) {\n rateLimitResetAt = new Date(Date.now() + 60 * 1000);\n }\n\n this.logger.warn(`Rate limited for credential ${credentialId}, reset at ${rateLimitResetAt.toISOString()}`);\n\n try {\n await this.store.updateCredentialStatus(context, credentialId, CredentialStatus.RATE_LIMITED, {\n rateLimitResetAt,\n incrementFailCount: true,\n });\n } catch (updateError) {\n this.logger.error(`Failed to update credential status: ${updateError}`);\n }\n }\n\n private async getProviderConfig(\n context: StoreContext | undefined,\n ): Promise<{\n baseURL: string;\n apiKey: string;\n proxy?: string;\n defaultModel?: string;\n credentialId?: string;\n } | null> {\n let config: Awaited<ReturnType<PodChatKitStore['getAiConfig']>> | undefined;\n\n if (context) {\n try {\n config = await this.store.getAiConfig(context);\n this.logger.debug(`Pod config: ${JSON.stringify(config)}`);\n } catch (error) {\n this.logger.debug(`Failed to get Pod config: ${error}`);\n config = undefined;\n }\n }\n\n // 用户 Pod 有配置,优先使用\n if (config?.apiKey) {\n return {\n baseURL: config.baseUrl || this.getDefaultBaseUrl('openrouter'),\n apiKey: config.apiKey,\n proxy: config.proxyUrl,\n credentialId: config.credentialId,\n };\n }\n\n // 环境变量配置(开发/测试用)\n if (process.env.XPOD_AI_API_KEY) {\n return {\n baseURL: process.env.XPOD_AI_BASE_URL || 'https://openrouter.ai/api/v1',\n apiKey: process.env.XPOD_AI_API_KEY,\n };\n }\n\n // Google API Key 特殊处理\n if (process.env.GOOGLE_API_KEY) {\n return {\n baseURL: 'https://generativelanguage.googleapis.com/v1beta/openai',\n apiKey: process.env.GOOGLE_API_KEY,\n };\n }\n\n // OpenRouter API Key\n if (process.env.OPENROUTER_API_KEY) {\n return {\n baseURL: 'https://openrouter.ai/api/v1',\n apiKey: process.env.OPENROUTER_API_KEY,\n };\n }\n\n // 无有效配置,返回 null 表示需要降级到 Default Agent\n this.logger.debug('No valid AI config found, will use Default Agent');\n return null;\n }\n\n private createProvider(config: { baseURL: string; apiKey: string; proxy?: string }) {\n const { baseURL, apiKey, proxy } = config;\n\n this.logger.debug(`Using AI Provider: ${baseURL} (proxy: ${proxy || 'none'})`);\n\n const options: any = { baseURL, apiKey };\n if (proxy) {\n options.fetch = createProxyFetch(proxy);\n }\n\n return createOpenAI(options);\n }\n\n private getPodBaseUrlFromWebId(webId: string): string {\n try {\n const url = new URL(webId);\n url.hash = ''; // 清除 fragment\n const pathParts = url.pathname.split('/');\n if (pathParts.includes('profile')) {\n const profileIndex = pathParts.indexOf('profile');\n url.pathname = pathParts.slice(0, profileIndex).join('/');\n }\n return url.toString().replace(/\\/$/, '') + '/';\n } catch {\n return '';\n }\n }\n\n private getDefaultBaseUrl(provider: string): string {\n const urls: Record<string, string> = {\n openai: 'https://api.openai.com/v1',\n google: 'https://generativelanguage.googleapis.com/v1beta/openai',\n anthropic: 'https://api.anthropic.com/v1',\n deepseek: 'https://api.deepseek.com/v1',\n openrouter: 'https://openrouter.ai/api/v1',\n ollama: 'http://localhost:11434/v1',\n mistral: 'https://api.mistral.ai/v1',\n cohere: 'https://api.cohere.ai/v1',\n zhipu: 'https://open.bigmodel.cn/api/paas/v4',\n };\n return urls[provider.toLowerCase()] || urls.openrouter;\n }\n\n /**\n * 使用 Default Agent 流式响应\n */\n private async *streamWithDefaultAgent(\n messages: Array<{ role: 'system' | 'user' | 'assistant'; content: string }>,\n context: StoreContext | undefined,\n ): AsyncIterable<string> {\n // 检查 Default Agent 是否可用\n if (!isDefaultAgentAvailable()) {\n yield '抱歉,您还没有配置 AI 服务,且系统默认 AI 也未配置。请先配置您的 AI API Key。';\n return;\n }\n\n // 构建 Default Agent 上下文\n const auth = context?.auth as AuthContext | undefined;\n const webId = auth ? getWebId(auth) : undefined;\n\n if (!webId) {\n yield '抱歉,无法获取您的身份信息,请先登录。';\n return;\n }\n\n const podBaseUrl = this.getPodBaseUrlFromWebId(webId);\n const solidToken = this.getSolidToken(context);\n\n if (!solidToken) {\n yield '抱歉,无法获取访问令牌,请重新登录。';\n return;\n }\n\n const agentContext: DefaultAgentContext = {\n solidToken,\n podBaseUrl,\n webId,\n };\n\n // 获取最后一条用户消息\n const lastUserMessage = messages.filter(m => m.role === 'user').pop()?.content || '';\n\n try {\n yield* streamDefaultAgent(lastUserMessage, agentContext);\n } catch (error) {\n this.logger.error(`Default Agent error: ${error}`);\n yield `抱歉,Default Agent 出现错误:${error instanceof Error ? error.message : String(error)}`;\n }\n }\n\n /**\n * 从上下文获取 Solid Token\n */\n private getSolidToken(context: StoreContext | undefined): string | undefined {\n // 尝试从 context 中获取 token\n // 这里需要根据实际的 auth 实现来获取\n const auth = context?.auth as AuthContext | undefined;\n if (!auth) return undefined;\n\n // 如果 auth 中有 token,直接返回\n if ('token' in auth && typeof auth.token === 'string') {\n return auth.token;\n }\n\n // 如果有 accessToken\n if ('accessToken' in auth && typeof auth.accessToken === 'string') {\n return auth.accessToken;\n }\n\n // 尝试从 credentials 获取\n if ('credentials' in auth && auth.credentials) {\n const creds = auth.credentials as any;\n if (creds.accessToken) return creds.accessToken;\n if (creds.token) return creds.token;\n }\n\n return undefined;\n }\n}\n"]}
|
|
1
|
+
{"version":3,"file":"ai-provider.js","sourceRoot":"","sources":["../../../src/api/chatkit/ai-provider.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * AI Provider Adapter\n *\n * Adapts existing AI services to the ChatKit AiProvider interface.\n * Includes 429 rate limit handling with credential status backfill.\n */\n\nimport { createOpenAI } from '@ai-sdk/openai';\nimport { streamText, APICallError } from 'ai';\nimport { getLoggerFor } from 'global-logger-factory';\nimport { ProxyAgent } from 'undici';\nimport type { AiProvider } from './service';\nimport type { StoreContext } from './store';\nimport type { PodChatKitStore } from './pod-store';\nimport type { AuthContext } from '../auth/AuthContext';\nimport { getWebId, getAccountId } from '../auth/AuthContext';\nimport { CredentialStatus } from '../../credential/schema/types';\nimport { getDefaultBaseUrl } from '../service/provider-registry';\n\n// Create a proxy-aware fetch function\nfunction createProxyFetch(proxyUrl: string): typeof fetch {\n const agent = new ProxyAgent(proxyUrl);\n return (url, init) => fetch(url, { ...init, dispatcher: agent } as any);\n}\n\nexport interface VercelAiProviderOptions {\n store: PodChatKitStore;\n}\n\n/**\n * Vercel AI SDK based provider\n *\n * Uses PodChatKitStore to get AI provider settings from Pod.\n * Reuses the same Session cached in StoreContext.\n */\nexport class VercelAiProvider implements AiProvider {\n private readonly logger = getLoggerFor(this);\n private readonly store: PodChatKitStore;\n\n public constructor(options: VercelAiProviderOptions) {\n this.store = options.store;\n }\n\n /**\n * Stream a response for the given messages\n */\n public async *streamResponse(\n messages: Array<{ role: 'system' | 'user' | 'assistant'; content: string }>,\n options?: {\n model?: string;\n temperature?: number;\n maxTokens?: number;\n context?: unknown;\n },\n ): AsyncIterable<string> {\n // Get context (contains auth and cached session)\n const context = options?.context as StoreContext | undefined;\n const auth = context?.auth as AuthContext | undefined;\n const userId = auth ? (getWebId(auth) ?? getAccountId(auth) ?? 'anonymous') : 'anonymous';\n\n // 从 Pod 获取配置\n const config = await this.getProviderConfig(context);\n\n if (!config) {\n throw new Error('No AI provider configured. Please configure Pod AI provider or set DEFAULT_API_BASE.');\n }\n\n const model = options?.model ?? config.defaultModel ?? process.env.DEFAULT_MODEL ?? 'stepfun/step-3.5-flash:free';\n\n this.logger.debug(`Streaming response for ${userId}, model: ${model}`);\n\n const provider = this.createProvider(config);\n\n try {\n const result = streamText({\n model: provider.chat(model),\n messages: messages as any,\n temperature: options?.temperature,\n maxTokens: options?.maxTokens,\n system: `You are a helpful AI assistant running on Xpod (a Solid Pod-based platform).\n\nYour capabilities:\n1. Help users with various tasks\n2. Use user's configured provider/model to respond consistently\n\nCurrent AI Provider: ${config.baseURL.includes('openrouter') ? 'OpenRouter (Free)' : 'Custom'}\nModel: ${model}\n`,\n } as any);\n\n // Stream text chunks\n for await (const chunk of result.textStream) {\n yield chunk;\n }\n } catch (error) {\n // Handle 429 rate limit errors\n if (this.isRateLimitError(error)) {\n await this.handleRateLimitError(error, context, config.credentialId);\n }\n throw error;\n }\n }\n\n /**\n * Check if error is a 429 rate limit error\n */\n private isRateLimitError(error: unknown): boolean {\n if (error instanceof APICallError) {\n return error.statusCode === 429;\n }\n if (error && typeof error === 'object') {\n const err = error as any;\n return err.status === 429 || err.statusCode === 429 || err.code === 'rate_limit_exceeded';\n }\n return false;\n }\n\n /**\n * Handle 429 rate limit error by updating credential status\n */\n private async handleRateLimitError(\n error: unknown,\n context: StoreContext | undefined,\n credentialId: string | undefined,\n ): Promise<void> {\n if (!context || !credentialId) {\n this.logger.debug('Cannot update credential status: missing context or credentialId');\n return;\n }\n\n let rateLimitResetAt: Date | undefined;\n if (error instanceof APICallError && error.responseHeaders) {\n const retryAfter = error.responseHeaders['retry-after'];\n if (retryAfter) {\n const seconds = parseInt(retryAfter, 10);\n if (!isNaN(seconds)) {\n rateLimitResetAt = new Date(Date.now() + seconds * 1000);\n } else {\n const date = new Date(retryAfter);\n if (!isNaN(date.getTime())) {\n rateLimitResetAt = date;\n }\n }\n }\n }\n\n if (!rateLimitResetAt) {\n rateLimitResetAt = new Date(Date.now() + 60 * 1000);\n }\n\n this.logger.warn(`Rate limited for credential ${credentialId}, reset at ${rateLimitResetAt.toISOString()}`);\n\n try {\n await this.store.updateCredentialStatus(context, credentialId, CredentialStatus.RATE_LIMITED, {\n rateLimitResetAt,\n incrementFailCount: true,\n });\n } catch (updateError) {\n this.logger.error(`Failed to update credential status: ${updateError}`);\n }\n }\n\n private async getProviderConfig(\n context: StoreContext | undefined,\n ): Promise<{\n baseURL: string;\n apiKey: string;\n proxy?: string;\n defaultModel?: string;\n credentialId?: string;\n } | null> {\n let config: Awaited<ReturnType<PodChatKitStore['getAiConfig']>> | undefined;\n\n if (context) {\n try {\n config = await this.store.getAiConfig(context);\n this.logger.debug(`Pod config: ${JSON.stringify(config)}`);\n } catch (error) {\n this.logger.debug(`Failed to get Pod config: ${error}`);\n config = undefined;\n }\n }\n\n // 1. 用户 Pod 有配置,优先使用\n if (config?.apiKey) {\n return {\n baseURL: config.baseUrl || getDefaultBaseUrl(),\n apiKey: config.apiKey,\n proxy: config.proxyUrl,\n credentialId: config.credentialId,\n };\n }\n\n // 2. 平台 Provider\n const platformBase = process.env.DEFAULT_API_BASE;\n if (platformBase) {\n return {\n baseURL: platformBase,\n apiKey: process.env.DEFAULT_API_KEY || '',\n };\n }\n\n // 3. 无配置\n this.logger.debug('No valid AI config found');\n return null;\n }\n\n private createProvider(config: { baseURL: string; apiKey: string; proxy?: string }) {\n const { baseURL, apiKey, proxy } = config;\n\n this.logger.debug(`Using AI Provider: ${baseURL} (proxy: ${proxy || 'none'})`);\n\n const options: any = { baseURL, apiKey };\n if (proxy) {\n options.fetch = createProxyFetch(proxy);\n }\n\n return createOpenAI(options);\n }\n\n}\n"]}
|