elasticdash-sdk 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +775 -0
- package/dist/browser-ui.d.ts +43 -0
- package/dist/browser-ui.d.ts.map +1 -0
- package/dist/browser-ui.js +246 -0
- package/dist/browser-ui.js.map +1 -0
- package/dist/capture/event.d.ts +33 -0
- package/dist/capture/event.d.ts.map +1 -0
- package/dist/capture/event.js +2 -0
- package/dist/capture/event.js.map +1 -0
- package/dist/capture/index.d.ts +4 -0
- package/dist/capture/index.d.ts.map +1 -0
- package/dist/capture/index.js +4 -0
- package/dist/capture/index.js.map +1 -0
- package/dist/capture/recorder.d.ts +24 -0
- package/dist/capture/recorder.d.ts.map +1 -0
- package/dist/capture/recorder.js +46 -0
- package/dist/capture/recorder.js.map +1 -0
- package/dist/capture/replay.d.ts +20 -0
- package/dist/capture/replay.d.ts.map +1 -0
- package/dist/capture/replay.js +47 -0
- package/dist/capture/replay.js.map +1 -0
- package/dist/ci/api-client.d.ts +38 -0
- package/dist/ci/api-client.d.ts.map +1 -0
- package/dist/ci/api-client.js +96 -0
- package/dist/ci/api-client.js.map +1 -0
- package/dist/ci/benchmark.d.ts +33 -0
- package/dist/ci/benchmark.d.ts.map +1 -0
- package/dist/ci/benchmark.js +213 -0
- package/dist/ci/benchmark.js.map +1 -0
- package/dist/ci/ed-runner.d.ts +48 -0
- package/dist/ci/ed-runner.d.ts.map +1 -0
- package/dist/ci/ed-runner.js +260 -0
- package/dist/ci/ed-runner.js.map +1 -0
- package/dist/ci/executor.d.ts +13 -0
- package/dist/ci/executor.d.ts.map +1 -0
- package/dist/ci/executor.js +542 -0
- package/dist/ci/executor.js.map +1 -0
- package/dist/ci/git-info.d.ts +17 -0
- package/dist/ci/git-info.d.ts.map +1 -0
- package/dist/ci/git-info.js +102 -0
- package/dist/ci/git-info.js.map +1 -0
- package/dist/ci/index.d.ts +6 -0
- package/dist/ci/index.d.ts.map +1 -0
- package/dist/ci/index.js +4 -0
- package/dist/ci/index.js.map +1 -0
- package/dist/ci/measurement.d.ts +9 -0
- package/dist/ci/measurement.d.ts.map +1 -0
- package/dist/ci/measurement.js +15 -0
- package/dist/ci/measurement.js.map +1 -0
- package/dist/ci/replay.d.ts +31 -0
- package/dist/ci/replay.d.ts.map +1 -0
- package/dist/ci/replay.js +96 -0
- package/dist/ci/replay.js.map +1 -0
- package/dist/ci/reporters/default.d.ts +8 -0
- package/dist/ci/reporters/default.d.ts.map +1 -0
- package/dist/ci/reporters/default.js +46 -0
- package/dist/ci/reporters/default.js.map +1 -0
- package/dist/ci/reporters/index.d.ts +8 -0
- package/dist/ci/reporters/index.d.ts.map +1 -0
- package/dist/ci/reporters/index.js +14 -0
- package/dist/ci/reporters/index.js.map +1 -0
- package/dist/ci/reporters/json.d.ts +8 -0
- package/dist/ci/reporters/json.d.ts.map +1 -0
- package/dist/ci/reporters/json.js +14 -0
- package/dist/ci/reporters/json.js.map +1 -0
- package/dist/ci/reporters/junit.d.ts +8 -0
- package/dist/ci/reporters/junit.d.ts.map +1 -0
- package/dist/ci/reporters/junit.js +48 -0
- package/dist/ci/reporters/junit.js.map +1 -0
- package/dist/ci/runner.d.ts +3 -0
- package/dist/ci/runner.d.ts.map +1 -0
- package/dist/ci/runner.js +187 -0
- package/dist/ci/runner.js.map +1 -0
- package/dist/ci/test-discovery.d.ts +5 -0
- package/dist/ci/test-discovery.d.ts.map +1 -0
- package/dist/ci/test-discovery.js +11 -0
- package/dist/ci/test-discovery.js.map +1 -0
- package/dist/ci/test-loader.d.ts +19 -0
- package/dist/ci/test-loader.d.ts.map +1 -0
- package/dist/ci/test-loader.js +149 -0
- package/dist/ci/test-loader.js.map +1 -0
- package/dist/ci/test-registry.d.ts +42 -0
- package/dist/ci/test-registry.d.ts.map +1 -0
- package/dist/ci/test-registry.js +18 -0
- package/dist/ci/test-registry.js.map +1 -0
- package/dist/ci/trace-schema.d.ts +30 -0
- package/dist/ci/trace-schema.d.ts.map +1 -0
- package/dist/ci/trace-schema.js +66 -0
- package/dist/ci/trace-schema.js.map +1 -0
- package/dist/ci/trace-writer.d.ts +16 -0
- package/dist/ci/trace-writer.d.ts.map +1 -0
- package/dist/ci/trace-writer.js +108 -0
- package/dist/ci/trace-writer.js.map +1 -0
- package/dist/ci/types.d.ts +108 -0
- package/dist/ci/types.d.ts.map +1 -0
- package/dist/ci/types.js +3 -0
- package/dist/ci/types.js.map +1 -0
- package/dist/ci/upload-client.d.ts +74 -0
- package/dist/ci/upload-client.d.ts.map +1 -0
- package/dist/ci/upload-client.js +195 -0
- package/dist/ci/upload-client.js.map +1 -0
- package/dist/cli.d.ts +3 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +716 -0
- package/dist/cli.js.map +1 -0
- package/dist/core/agent-state.d.ts +47 -0
- package/dist/core/agent-state.d.ts.map +1 -0
- package/dist/core/agent-state.js +137 -0
- package/dist/core/agent-state.js.map +1 -0
- package/dist/core/judge-utils.d.ts +22 -0
- package/dist/core/judge-utils.d.ts.map +1 -0
- package/dist/core/judge-utils.js +211 -0
- package/dist/core/judge-utils.js.map +1 -0
- package/dist/core/registry.d.ts +28 -0
- package/dist/core/registry.d.ts.map +1 -0
- package/dist/core/registry.js +52 -0
- package/dist/core/registry.js.map +1 -0
- package/dist/dashboard-server.d.ts +65 -0
- package/dist/dashboard-server.d.ts.map +1 -0
- package/dist/dashboard-server.js +3940 -0
- package/dist/dashboard-server.js.map +1 -0
- package/dist/execution/tool-runner.d.ts +26 -0
- package/dist/execution/tool-runner.d.ts.map +1 -0
- package/dist/execution/tool-runner.js +316 -0
- package/dist/execution/tool-runner.js.map +1 -0
- package/dist/html/dashboard.html +2218 -0
- package/dist/http.d.ts +14 -0
- package/dist/http.d.ts.map +1 -0
- package/dist/http.js +13 -0
- package/dist/http.js.map +1 -0
- package/dist/index.cjs +8102 -0
- package/dist/index.d.ts +61 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +67 -0
- package/dist/index.js.map +1 -0
- package/dist/interceptors/ai-interceptor.d.ts +26 -0
- package/dist/interceptors/ai-interceptor.d.ts.map +1 -0
- package/dist/interceptors/ai-interceptor.js +756 -0
- package/dist/interceptors/ai-interceptor.js.map +1 -0
- package/dist/interceptors/db-auto.d.ts +8 -0
- package/dist/interceptors/db-auto.d.ts.map +1 -0
- package/dist/interceptors/db-auto.js +217 -0
- package/dist/interceptors/db-auto.js.map +1 -0
- package/dist/interceptors/db.d.ts +23 -0
- package/dist/interceptors/db.d.ts.map +1 -0
- package/dist/interceptors/db.js +137 -0
- package/dist/interceptors/db.js.map +1 -0
- package/dist/interceptors/http.d.ts +28 -0
- package/dist/interceptors/http.d.ts.map +1 -0
- package/dist/interceptors/http.js +356 -0
- package/dist/interceptors/http.js.map +1 -0
- package/dist/interceptors/side-effects.d.ts +7 -0
- package/dist/interceptors/side-effects.d.ts.map +1 -0
- package/dist/interceptors/side-effects.js +72 -0
- package/dist/interceptors/side-effects.js.map +1 -0
- package/dist/interceptors/telemetry-push.d.ts +142 -0
- package/dist/interceptors/telemetry-push.d.ts.map +1 -0
- package/dist/interceptors/telemetry-push.js +463 -0
- package/dist/interceptors/telemetry-push.js.map +1 -0
- package/dist/interceptors/tool.d.ts +2 -0
- package/dist/interceptors/tool.d.ts.map +1 -0
- package/dist/interceptors/tool.js +274 -0
- package/dist/interceptors/tool.js.map +1 -0
- package/dist/interceptors/workflow-ai.d.ts +5 -0
- package/dist/interceptors/workflow-ai.d.ts.map +1 -0
- package/dist/interceptors/workflow-ai.js +382 -0
- package/dist/interceptors/workflow-ai.js.map +1 -0
- package/dist/internals/conditional-recorder.d.ts +21 -0
- package/dist/internals/conditional-recorder.d.ts.map +1 -0
- package/dist/internals/conditional-recorder.js +54 -0
- package/dist/internals/conditional-recorder.js.map +1 -0
- package/dist/internals/mock-resolver.d.ts +146 -0
- package/dist/internals/mock-resolver.d.ts.map +1 -0
- package/dist/internals/mock-resolver.js +427 -0
- package/dist/internals/mock-resolver.js.map +1 -0
- package/dist/matchers/index.d.ts +96 -0
- package/dist/matchers/index.d.ts.map +1 -0
- package/dist/matchers/index.js +668 -0
- package/dist/matchers/index.js.map +1 -0
- package/dist/observability.d.ts +82 -0
- package/dist/observability.d.ts.map +1 -0
- package/dist/observability.js +471 -0
- package/dist/observability.js.map +1 -0
- package/dist/portal-executor.d.ts +30 -0
- package/dist/portal-executor.d.ts.map +1 -0
- package/dist/portal-executor.js +324 -0
- package/dist/portal-executor.js.map +1 -0
- package/dist/portal-server.d.ts +3 -0
- package/dist/portal-server.d.ts.map +1 -0
- package/dist/portal-server.js +279 -0
- package/dist/portal-server.js.map +1 -0
- package/dist/proxy/llm-capture.d.ts +14 -0
- package/dist/proxy/llm-capture.d.ts.map +1 -0
- package/dist/proxy/llm-capture.js +264 -0
- package/dist/proxy/llm-capture.js.map +1 -0
- package/dist/reporter.d.ts +3 -0
- package/dist/reporter.d.ts.map +1 -0
- package/dist/reporter.js +72 -0
- package/dist/reporter.js.map +1 -0
- package/dist/runWorkflowSubprocess.d.ts +14 -0
- package/dist/runWorkflowSubprocess.d.ts.map +1 -0
- package/dist/runWorkflowSubprocess.js +66 -0
- package/dist/runWorkflowSubprocess.js.map +1 -0
- package/dist/runner.d.ts +16 -0
- package/dist/runner.d.ts.map +1 -0
- package/dist/runner.js +138 -0
- package/dist/runner.js.map +1 -0
- package/dist/socket-connector.d.ts +22 -0
- package/dist/socket-connector.d.ts.map +1 -0
- package/dist/socket-connector.js +104 -0
- package/dist/socket-connector.js.map +1 -0
- package/dist/telemetry-batcher.d.ts +56 -0
- package/dist/telemetry-batcher.d.ts.map +1 -0
- package/dist/telemetry-batcher.js +143 -0
- package/dist/telemetry-batcher.js.map +1 -0
- package/dist/test-setup.d.ts +12 -0
- package/dist/test-setup.d.ts.map +1 -0
- package/dist/test-setup.js +13 -0
- package/dist/test-setup.js.map +1 -0
- package/dist/tool-registry.d.ts +31 -0
- package/dist/tool-registry.d.ts.map +1 -0
- package/dist/tool-registry.js +73 -0
- package/dist/tool-registry.js.map +1 -0
- package/dist/tool-runner-worker.d.ts +2 -0
- package/dist/tool-runner-worker.d.ts.map +1 -0
- package/dist/tool-runner-worker.js +215 -0
- package/dist/tool-runner-worker.js.map +1 -0
- package/dist/trace-adapter/context.d.ts +72 -0
- package/dist/trace-adapter/context.d.ts.map +1 -0
- package/dist/trace-adapter/context.js +80 -0
- package/dist/trace-adapter/context.js.map +1 -0
- package/dist/tracing.d.ts +2 -0
- package/dist/tracing.d.ts.map +1 -0
- package/dist/tracing.js +59 -0
- package/dist/tracing.js.map +1 -0
- package/dist/trigger-executor.d.ts +12 -0
- package/dist/trigger-executor.d.ts.map +1 -0
- package/dist/trigger-executor.js +130 -0
- package/dist/trigger-executor.js.map +1 -0
- package/dist/types/portal.d.ts +76 -0
- package/dist/types/portal.d.ts.map +1 -0
- package/dist/types/portal.js +2 -0
- package/dist/types/portal.js.map +1 -0
- package/dist/utils/debug.d.ts +3 -0
- package/dist/utils/debug.d.ts.map +1 -0
- package/dist/utils/debug.js +8 -0
- package/dist/utils/debug.js.map +1 -0
- package/dist/utils/license-error.d.ts +23 -0
- package/dist/utils/license-error.d.ts.map +1 -0
- package/dist/utils/license-error.js +42 -0
- package/dist/utils/license-error.js.map +1 -0
- package/dist/utils/redact.d.ts +7 -0
- package/dist/utils/redact.d.ts.map +1 -0
- package/dist/utils/redact.js +26 -0
- package/dist/utils/redact.js.map +1 -0
- package/dist/workflow-runner-worker.d.ts +2 -0
- package/dist/workflow-runner-worker.d.ts.map +1 -0
- package/dist/workflow-runner-worker.js +329 -0
- package/dist/workflow-runner-worker.js.map +1 -0
- package/dist/workflow-runner.d.ts +14 -0
- package/dist/workflow-runner.d.ts.map +1 -0
- package/dist/workflow-runner.js +34 -0
- package/dist/workflow-runner.js.map +1 -0
- package/docs/agent-coding-instructions.md +138 -0
- package/docs/agent-integration-guide.md +564 -0
- package/docs/agents.md +140 -0
- package/docs/dashboard.md +394 -0
- package/docs/deno.md +69 -0
- package/docs/instrumentation.md +424 -0
- package/docs/langfuse-trace-structure.md +145 -0
- package/docs/matchers.md +173 -0
- package/docs/observability_contract.md +192 -0
- package/docs/observability_mode.md +195 -0
- package/docs/quickstart.md +621 -0
- package/docs/security-compliance.md +566 -0
- package/docs/test-writing-guidelines.md +444 -0
- package/docs/tools.md +165 -0
- package/docs/workflow-modes.md +253 -0
- package/package.json +76 -0
- package/src/browser-ui.ts +281 -0
- package/src/capture/event.ts +30 -0
- package/src/capture/index.ts +3 -0
- package/src/capture/recorder.ts +62 -0
- package/src/capture/replay.ts +55 -0
- package/src/ci/api-client.ts +136 -0
- package/src/ci/benchmark.ts +257 -0
- package/src/ci/ed-runner.ts +351 -0
- package/src/ci/executor.ts +671 -0
- package/src/ci/git-info.ts +127 -0
- package/src/ci/index.ts +5 -0
- package/src/ci/measurement.ts +25 -0
- package/src/ci/replay.ts +127 -0
- package/src/ci/reporters/default.ts +50 -0
- package/src/ci/reporters/index.ts +21 -0
- package/src/ci/reporters/json.ts +18 -0
- package/src/ci/reporters/junit.ts +61 -0
- package/src/ci/runner.ts +208 -0
- package/src/ci/test-discovery.ts +16 -0
- package/src/ci/test-loader.ts +187 -0
- package/src/ci/test-registry.ts +62 -0
- package/src/ci/trace-schema.ts +96 -0
- package/src/ci/trace-writer.ts +107 -0
- package/src/ci/types.ts +115 -0
- package/src/ci/upload-client.ts +300 -0
- package/src/cli.ts +811 -0
- package/src/core/agent-state.ts +162 -0
- package/src/core/judge-utils.ts +232 -0
- package/src/core/registry.ts +92 -0
- package/src/dashboard-server.ts +2047 -0
- package/src/execution/tool-runner.ts +352 -0
- package/src/html/dashboard.html +2218 -0
- package/src/http.ts +13 -0
- package/src/index.ts +138 -0
- package/src/interceptors/ai-interceptor.ts +798 -0
- package/src/interceptors/db-auto.ts +243 -0
- package/src/interceptors/db.ts +156 -0
- package/src/interceptors/http.ts +393 -0
- package/src/interceptors/side-effects.ts +83 -0
- package/src/interceptors/telemetry-push.ts +537 -0
- package/src/interceptors/tool.ts +287 -0
- package/src/interceptors/workflow-ai.ts +419 -0
- package/src/internals/conditional-recorder.ts +63 -0
- package/src/internals/mock-resolver.ts +492 -0
- package/src/matchers/index.ts +824 -0
- package/src/observability.ts +501 -0
- package/src/portal-executor.ts +355 -0
- package/src/portal-server.ts +304 -0
- package/src/proxy/llm-capture.ts +301 -0
- package/src/reporter.ts +81 -0
- package/src/runWorkflowSubprocess.ts +74 -0
- package/src/runner.ts +178 -0
- package/src/socket-connector.ts +117 -0
- package/src/telemetry-batcher.ts +191 -0
- package/src/test-setup.ts +16 -0
- package/src/tool-registry.ts +94 -0
- package/src/tool-runner-worker.ts +244 -0
- package/src/trace-adapter/context.ts +156 -0
- package/src/tracing.ts +62 -0
- package/src/trigger-executor.ts +171 -0
- package/src/types/agent.d.ts +63 -0
- package/src/types/expect.d.ts +81 -0
- package/src/types/modules.d.ts +2 -0
- package/src/types/portal.ts +69 -0
- package/src/utils/debug.ts +8 -0
- package/src/utils/license-error.ts +43 -0
- package/src/utils/redact.ts +25 -0
- package/src/workflow-runner-worker.ts +386 -0
- package/src/workflow-runner.ts +58 -0
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import type { ToolInfo } from './execution/tool-runner.js';
|
|
2
|
+
import type { PortalTask, PortalTaskResult } from './types/portal.js';
|
|
3
|
+
export interface AvailabilityResult {
|
|
4
|
+
available: boolean;
|
|
5
|
+
reason?: string;
|
|
6
|
+
}
|
|
7
|
+
/**
|
|
8
|
+
* Check if a tool is available for execution.
|
|
9
|
+
* Returns { available: true } or { available: false, reason: "..." }.
|
|
10
|
+
*/
|
|
11
|
+
export declare function checkToolAvailability(name: string, cwd: string, tools: ToolInfo[]): AvailabilityResult;
|
|
12
|
+
/**
|
|
13
|
+
* Check if an AI provider is available for execution (API key present).
|
|
14
|
+
* Returns { available: true } or { available: false, reason: "..." }.
|
|
15
|
+
*/
|
|
16
|
+
export declare function checkAIAvailability(provider?: string, model?: string): AvailabilityResult;
|
|
17
|
+
/**
|
|
18
|
+
* Execute a portal task (tool or AI rerun). Returns a structured result with
|
|
19
|
+
* output, duration, usage, and error information.
|
|
20
|
+
*
|
|
21
|
+
* Error handling:
|
|
22
|
+
* - Tool not found in ed_tools → ok:false with descriptive error
|
|
23
|
+
* - ed_tools.ts/js missing → ok:false with descriptive error
|
|
24
|
+
* - AI provider unsupported → ok:false with error
|
|
25
|
+
* - AI provider API key missing → ok:false with error naming the expected env var
|
|
26
|
+
* - Subprocess crash → ok:false with stderr excerpt
|
|
27
|
+
* - Any other exception → ok:false with error message
|
|
28
|
+
*/
|
|
29
|
+
export declare function executePortalTask(task: PortalTask, cwd: string, tools: ToolInfo[]): Promise<PortalTaskResult>;
|
|
30
|
+
//# sourceMappingURL=portal-executor.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"portal-executor.d.ts","sourceRoot":"","sources":["../src/portal-executor.ts"],"names":[],"mappings":"AAOA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,4BAA4B,CAAA;AAC1D,OAAO,KAAK,EAAE,UAAU,EAAE,gBAAgB,EAAE,MAAM,mBAAmB,CAAA;AAiGrE,MAAM,WAAW,kBAAkB;IACjC,SAAS,EAAE,OAAO,CAAA;IAClB,MAAM,CAAC,EAAE,MAAM,CAAA;CAChB;AAED;;;GAGG;AACH,wBAAgB,qBAAqB,CACnC,IAAI,EAAE,MAAM,EACZ,GAAG,EAAE,MAAM,EACX,KAAK,EAAE,QAAQ,EAAE,GAChB,kBAAkB,CAcpB;AAED;;;GAGG;AACH,wBAAgB,mBAAmB,CACjC,QAAQ,CAAC,EAAE,MAAM,EACjB,KAAK,CAAC,EAAE,MAAM,GACb,kBAAkB,CAcpB;AAiBD;;;;;;;;;;;GAWG;AACH,wBAAsB,iBAAiB,CACrC,IAAI,EAAE,UAAU,EAChB,GAAG,EAAE,MAAM,EACX,KAAK,EAAE,QAAQ,EAAE,GAChB,OAAO,CAAC,gBAAgB,CAAC,CA2B3B"}
|
|
@@ -0,0 +1,324 @@
|
|
|
1
|
+
import { callProviderLLM } from './matchers/index.js';
|
|
2
|
+
import { resolveRuntimeModule, buildToolArgs, runToolInSubprocess, } from './execution/tool-runner.js';
|
|
3
|
+
import { debugLog } from './utils/debug.js';
|
|
4
|
+
/** Provider → required env var name */
|
|
5
|
+
const PROVIDER_API_KEY_ENV = {
|
|
6
|
+
openai: 'OPENAI_API_KEY',
|
|
7
|
+
claude: 'ANTHROPIC_API_KEY',
|
|
8
|
+
gemini: 'GEMINI_API_KEY',
|
|
9
|
+
grok: 'GROK_API_KEY',
|
|
10
|
+
kimi: 'KIMI_API_KEY',
|
|
11
|
+
};
|
|
12
|
+
// ---------------------------------------------------------------------------
|
|
13
|
+
// Prompt extraction (mirrors dashboard-server.ts logic)
|
|
14
|
+
// ---------------------------------------------------------------------------
|
|
15
|
+
function normalizeMessageContent(content) {
|
|
16
|
+
if (typeof content === 'string')
|
|
17
|
+
return content;
|
|
18
|
+
if (Array.isArray(content)) {
|
|
19
|
+
return content
|
|
20
|
+
.map((part) => {
|
|
21
|
+
if (typeof part === 'string')
|
|
22
|
+
return part;
|
|
23
|
+
if (part && typeof part === 'object' && typeof part.text === 'string')
|
|
24
|
+
return part.text;
|
|
25
|
+
try {
|
|
26
|
+
return JSON.stringify(part);
|
|
27
|
+
}
|
|
28
|
+
catch {
|
|
29
|
+
return String(part);
|
|
30
|
+
}
|
|
31
|
+
})
|
|
32
|
+
.join('\n');
|
|
33
|
+
}
|
|
34
|
+
if (content && typeof content === 'object') {
|
|
35
|
+
if (typeof content.text === 'string')
|
|
36
|
+
return content.text;
|
|
37
|
+
try {
|
|
38
|
+
return JSON.stringify(content);
|
|
39
|
+
}
|
|
40
|
+
catch {
|
|
41
|
+
return String(content);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
return content == null ? '' : String(content);
|
|
45
|
+
}
|
|
46
|
+
function extractPromptFromInput(input) {
|
|
47
|
+
if (typeof input === 'string')
|
|
48
|
+
return { prompt: input };
|
|
49
|
+
const messages = Array.isArray(input)
|
|
50
|
+
? input
|
|
51
|
+
: input && typeof input === 'object' && Array.isArray(input.messages)
|
|
52
|
+
? input.messages
|
|
53
|
+
: null;
|
|
54
|
+
if (messages && messages.length > 0) {
|
|
55
|
+
const systemParts = [];
|
|
56
|
+
const promptParts = [];
|
|
57
|
+
for (const message of messages) {
|
|
58
|
+
const role = typeof message?.role === 'string' ? message.role : 'user';
|
|
59
|
+
const content = normalizeMessageContent(message?.content).trim();
|
|
60
|
+
if (!content)
|
|
61
|
+
continue;
|
|
62
|
+
if (role === 'system')
|
|
63
|
+
systemParts.push(content);
|
|
64
|
+
else
|
|
65
|
+
promptParts.push(`${role}: ${content}`);
|
|
66
|
+
}
|
|
67
|
+
return {
|
|
68
|
+
prompt: promptParts.join('\n\n') || systemParts.join('\n\n') || JSON.stringify(input),
|
|
69
|
+
systemPrompt: systemParts.length > 0 ? systemParts.join('\n\n') : undefined,
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
if (input && typeof input === 'object' && typeof input.prompt === 'string') {
|
|
73
|
+
return {
|
|
74
|
+
prompt: input.prompt,
|
|
75
|
+
systemPrompt: typeof input.systemPrompt === 'string' ? input.systemPrompt : undefined,
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
try {
|
|
79
|
+
return { prompt: JSON.stringify(input) };
|
|
80
|
+
}
|
|
81
|
+
catch {
|
|
82
|
+
return { prompt: String(input ?? '') };
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
function inferProvider(task) {
|
|
86
|
+
const provider = task.provider?.toLowerCase();
|
|
87
|
+
if (provider === 'openai' || provider === 'claude' || provider === 'gemini' || provider === 'grok' || provider === 'kimi') {
|
|
88
|
+
return provider;
|
|
89
|
+
}
|
|
90
|
+
// Try extracting provider from input (wrapAI attaches it to the event input)
|
|
91
|
+
const inputProvider = task.input && typeof task.input === 'object'
|
|
92
|
+
? task.input.provider
|
|
93
|
+
: undefined;
|
|
94
|
+
if (typeof inputProvider === 'string') {
|
|
95
|
+
const ip = inputProvider.toLowerCase();
|
|
96
|
+
if (ip === 'openai' || ip === 'claude' || ip === 'gemini' || ip === 'grok' || ip === 'kimi')
|
|
97
|
+
return ip;
|
|
98
|
+
}
|
|
99
|
+
const model = (task.model ?? task.name)?.toLowerCase() ?? '';
|
|
100
|
+
if (model.includes('claude'))
|
|
101
|
+
return 'claude';
|
|
102
|
+
if (model.includes('gemini'))
|
|
103
|
+
return 'gemini';
|
|
104
|
+
if (model.includes('grok'))
|
|
105
|
+
return 'grok';
|
|
106
|
+
if (model.includes('kimi'))
|
|
107
|
+
return 'kimi';
|
|
108
|
+
return 'openai';
|
|
109
|
+
}
|
|
110
|
+
/**
|
|
111
|
+
* Check if a tool is available for execution.
|
|
112
|
+
* Returns { available: true } or { available: false, reason: "..." }.
|
|
113
|
+
*/
|
|
114
|
+
export function checkToolAvailability(name, cwd, tools) {
|
|
115
|
+
if (!name) {
|
|
116
|
+
return { available: false, reason: 'Missing tool name.' };
|
|
117
|
+
}
|
|
118
|
+
const toolsModulePath = resolveRuntimeModule(cwd, 'ed_tools');
|
|
119
|
+
if (!toolsModulePath) {
|
|
120
|
+
return { available: false, reason: 'Cannot find ed_tools.ts/js in workspace root.' };
|
|
121
|
+
}
|
|
122
|
+
const toolInfo = tools.find(t => t.name === name);
|
|
123
|
+
if (!toolInfo) {
|
|
124
|
+
const available = tools.map(t => t.name).join(', ') || '(none found)';
|
|
125
|
+
return { available: false, reason: `Tool not found: "${name}". Available tools: ${available}` };
|
|
126
|
+
}
|
|
127
|
+
return { available: true };
|
|
128
|
+
}
|
|
129
|
+
/**
|
|
130
|
+
* Check if an AI provider is available for execution (API key present).
|
|
131
|
+
* Returns { available: true } or { available: false, reason: "..." }.
|
|
132
|
+
*/
|
|
133
|
+
export function checkAIAvailability(provider, model) {
|
|
134
|
+
const resolved = inferProviderFromArgs(provider, model);
|
|
135
|
+
const supportedProviders = ['openai', 'claude', 'gemini', 'grok', 'kimi'];
|
|
136
|
+
if (!supportedProviders.includes(resolved)) {
|
|
137
|
+
return { available: false, reason: `Unsupported AI provider: "${provider ?? model}". Supported: ${supportedProviders.join(', ')}` };
|
|
138
|
+
}
|
|
139
|
+
const envVar = PROVIDER_API_KEY_ENV[resolved];
|
|
140
|
+
if (!process.env[envVar]) {
|
|
141
|
+
if (resolved === 'gemini' && process.env.GOOGLE_API_KEY) {
|
|
142
|
+
return { available: true };
|
|
143
|
+
}
|
|
144
|
+
return { available: false, reason: `Missing API key for provider "${resolved}". Expected environment variable: ${envVar}` };
|
|
145
|
+
}
|
|
146
|
+
return { available: true };
|
|
147
|
+
}
|
|
148
|
+
function inferProviderFromArgs(provider, model) {
|
|
149
|
+
const p = provider?.toLowerCase();
|
|
150
|
+
if (p === 'openai' || p === 'claude' || p === 'gemini' || p === 'grok' || p === 'kimi')
|
|
151
|
+
return p;
|
|
152
|
+
const m = (model ?? '')?.toLowerCase();
|
|
153
|
+
if (m.includes('claude'))
|
|
154
|
+
return 'claude';
|
|
155
|
+
if (m.includes('gemini'))
|
|
156
|
+
return 'gemini';
|
|
157
|
+
if (m.includes('grok'))
|
|
158
|
+
return 'grok';
|
|
159
|
+
if (m.includes('kimi'))
|
|
160
|
+
return 'kimi';
|
|
161
|
+
return 'openai';
|
|
162
|
+
}
|
|
163
|
+
// ---------------------------------------------------------------------------
|
|
164
|
+
// Public API
|
|
165
|
+
// ---------------------------------------------------------------------------
|
|
166
|
+
/**
|
|
167
|
+
* Execute a portal task (tool or AI rerun). Returns a structured result with
|
|
168
|
+
* output, duration, usage, and error information.
|
|
169
|
+
*
|
|
170
|
+
* Error handling:
|
|
171
|
+
* - Tool not found in ed_tools → ok:false with descriptive error
|
|
172
|
+
* - ed_tools.ts/js missing → ok:false with descriptive error
|
|
173
|
+
* - AI provider unsupported → ok:false with error
|
|
174
|
+
* - AI provider API key missing → ok:false with error naming the expected env var
|
|
175
|
+
* - Subprocess crash → ok:false with stderr excerpt
|
|
176
|
+
* - Any other exception → ok:false with error message
|
|
177
|
+
*/
|
|
178
|
+
export async function executePortalTask(task, cwd, tools) {
|
|
179
|
+
const start = Date.now();
|
|
180
|
+
try {
|
|
181
|
+
if (task.type === 'tool') {
|
|
182
|
+
return await executeToolTask(task, cwd, tools);
|
|
183
|
+
}
|
|
184
|
+
if (task.type === 'ai') {
|
|
185
|
+
return await executeAITask(task);
|
|
186
|
+
}
|
|
187
|
+
return {
|
|
188
|
+
taskId: task.taskId,
|
|
189
|
+
ok: false,
|
|
190
|
+
output: null,
|
|
191
|
+
error: `Unknown task type: ${task.type}`,
|
|
192
|
+
durationMs: Date.now() - start,
|
|
193
|
+
metadata: task.metadata,
|
|
194
|
+
};
|
|
195
|
+
}
|
|
196
|
+
catch (e) {
|
|
197
|
+
return {
|
|
198
|
+
taskId: task.taskId,
|
|
199
|
+
ok: false,
|
|
200
|
+
output: null,
|
|
201
|
+
error: e instanceof Error ? e.message : String(e),
|
|
202
|
+
durationMs: Date.now() - start,
|
|
203
|
+
metadata: task.metadata,
|
|
204
|
+
};
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
async function executeToolTask(task, cwd, tools) {
|
|
208
|
+
const start = Date.now();
|
|
209
|
+
if (!task.name) {
|
|
210
|
+
return { taskId: task.taskId, ok: false, output: null, error: 'Missing tool name on task.', durationMs: 0, metadata: task.metadata };
|
|
211
|
+
}
|
|
212
|
+
// Check tool exists in the scanned tool list
|
|
213
|
+
const toolInfo = tools.find(t => t.name === task.name);
|
|
214
|
+
if (!toolInfo) {
|
|
215
|
+
const available = tools.map(t => t.name).join(', ') || '(none found)';
|
|
216
|
+
return {
|
|
217
|
+
taskId: task.taskId, ok: false, output: null,
|
|
218
|
+
error: `Tool not found: "${task.name}". Available tools: ${available}`,
|
|
219
|
+
durationMs: 0, metadata: task.metadata,
|
|
220
|
+
};
|
|
221
|
+
}
|
|
222
|
+
// Resolve ed_tools module
|
|
223
|
+
const toolsModulePath = resolveRuntimeModule(cwd, 'ed_tools');
|
|
224
|
+
if (!toolsModulePath) {
|
|
225
|
+
return {
|
|
226
|
+
taskId: task.taskId, ok: false, output: null,
|
|
227
|
+
error: 'Cannot find ed_tools.ts/js in workspace root.',
|
|
228
|
+
durationMs: 0, metadata: task.metadata,
|
|
229
|
+
};
|
|
230
|
+
}
|
|
231
|
+
// Parse input
|
|
232
|
+
let parsedInput = task.input;
|
|
233
|
+
if (typeof parsedInput === 'string') {
|
|
234
|
+
try {
|
|
235
|
+
parsedInput = JSON.parse(parsedInput);
|
|
236
|
+
}
|
|
237
|
+
catch { /* use as-is */ }
|
|
238
|
+
}
|
|
239
|
+
const args = buildToolArgs(parsedInput, toolInfo);
|
|
240
|
+
debugLog(`[elasticdash portal] Executing tool: ${task.name}`, { args });
|
|
241
|
+
const result = await runToolInSubprocess(toolsModulePath, task.name, args, task.frozenEvents)
|
|
242
|
+
.catch(err => {
|
|
243
|
+
const errorMsg = err instanceof Error ? err.stack || err.message : String(err);
|
|
244
|
+
debugLog(`[elasticdash portal] Tool execution failed: ${errorMsg}`);
|
|
245
|
+
throw new Error(`Tool execution failed: ${errorMsg}`);
|
|
246
|
+
});
|
|
247
|
+
const durationMs = result.currentDurationMs ?? (Date.now() - start);
|
|
248
|
+
debugLog(`[elasticdash portal] Tool execution completed: ${task.name}`, { ...result, durationMs });
|
|
249
|
+
return {
|
|
250
|
+
taskId: task.taskId,
|
|
251
|
+
ok: result.ok,
|
|
252
|
+
output: result.currentOutput ?? null,
|
|
253
|
+
error: result.error,
|
|
254
|
+
durationMs,
|
|
255
|
+
usage: result.currentUsage,
|
|
256
|
+
metadata: task.metadata,
|
|
257
|
+
};
|
|
258
|
+
}
|
|
259
|
+
async function executeAITask(task) {
|
|
260
|
+
const start = Date.now();
|
|
261
|
+
// Infer provider
|
|
262
|
+
const provider = inferProvider(task);
|
|
263
|
+
const supportedProviders = ['openai', 'claude', 'gemini', 'grok', 'kimi'];
|
|
264
|
+
if (!supportedProviders.includes(provider)) {
|
|
265
|
+
return {
|
|
266
|
+
taskId: task.taskId, ok: false, output: null,
|
|
267
|
+
error: `Unsupported AI provider: "${task.provider}". Supported: ${supportedProviders.join(', ')}`,
|
|
268
|
+
durationMs: 0, metadata: task.metadata,
|
|
269
|
+
};
|
|
270
|
+
}
|
|
271
|
+
// Check API key is available
|
|
272
|
+
const envVar = PROVIDER_API_KEY_ENV[provider];
|
|
273
|
+
if (!process.env[envVar]) {
|
|
274
|
+
// Gemini also accepts GOOGLE_API_KEY
|
|
275
|
+
if (provider === 'gemini' && process.env.GOOGLE_API_KEY) {
|
|
276
|
+
// ok, fallback key available
|
|
277
|
+
}
|
|
278
|
+
else {
|
|
279
|
+
return {
|
|
280
|
+
taskId: task.taskId, ok: false, output: null,
|
|
281
|
+
error: `Missing API key for provider "${provider}". Expected environment variable: ${envVar}`,
|
|
282
|
+
durationMs: 0, metadata: task.metadata,
|
|
283
|
+
};
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
// Extract prompt from input
|
|
287
|
+
const { prompt, systemPrompt } = extractPromptFromInput(task.input);
|
|
288
|
+
if (!prompt.trim()) {
|
|
289
|
+
return {
|
|
290
|
+
taskId: task.taskId, ok: false, output: null,
|
|
291
|
+
error: 'AI task input is empty; cannot execute.',
|
|
292
|
+
durationMs: 0, metadata: task.metadata,
|
|
293
|
+
};
|
|
294
|
+
}
|
|
295
|
+
// Prefer explicit model from trigger step, then try extracting from input
|
|
296
|
+
// (wrapAI attaches the actual API model name to the event input), fall back to event name
|
|
297
|
+
const inputModel = task.input && typeof task.input === 'object' && typeof task.input.model === 'string'
|
|
298
|
+
? task.input.model
|
|
299
|
+
: undefined;
|
|
300
|
+
const model = task.model ?? inputModel ?? task.name;
|
|
301
|
+
const temperature = typeof task.modelParameters?.temperature === 'number' ? task.modelParameters.temperature : 0;
|
|
302
|
+
const maxTokens = typeof task.modelParameters?.max_tokens === 'number' ? task.modelParameters.max_tokens : 512;
|
|
303
|
+
debugLog(`[elasticdash portal] Executing AI: provider=${provider} model=${model}`);
|
|
304
|
+
try {
|
|
305
|
+
const result = await callProviderLLM(prompt, { provider, model }, systemPrompt ?? 'You are a helpful assistant.', maxTokens);
|
|
306
|
+
return {
|
|
307
|
+
taskId: task.taskId,
|
|
308
|
+
ok: true,
|
|
309
|
+
output: result.content,
|
|
310
|
+
durationMs: result.durationMs ?? (Date.now() - start),
|
|
311
|
+
usage: result.usage,
|
|
312
|
+
metadata: task.metadata,
|
|
313
|
+
};
|
|
314
|
+
}
|
|
315
|
+
catch (e) {
|
|
316
|
+
return {
|
|
317
|
+
taskId: task.taskId, ok: false, output: null,
|
|
318
|
+
error: `AI execution failed: ${e instanceof Error ? e.message : String(e)}`,
|
|
319
|
+
durationMs: Date.now() - start,
|
|
320
|
+
metadata: task.metadata,
|
|
321
|
+
};
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
//# sourceMappingURL=portal-executor.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"portal-executor.js","sourceRoot":"","sources":["../src/portal-executor.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"portal-server.d.ts","sourceRoot":"","sources":["../src/portal-server.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAGV,mBAAmB,EACnB,kBAAkB,EAEnB,MAAM,mBAAmB,CAAA;AA6E1B,wBAAsB,iBAAiB,CAAC,OAAO,EAAE,mBAAmB,GAAG,OAAO,CAAC,kBAAkB,CAAC,CAuNjG"}
|
|
@@ -0,0 +1,279 @@
|
|
|
1
|
+
import express from 'express';
|
|
2
|
+
import http from 'node:http';
|
|
3
|
+
import { executePortalTask } from './portal-executor.js';
|
|
4
|
+
import { scanTools } from './execution/tool-runner.js';
|
|
5
|
+
import { debugLog } from './utils/debug.js';
|
|
6
|
+
// ---------------------------------------------------------------------------
|
|
7
|
+
// Origin allowlist
|
|
8
|
+
// ---------------------------------------------------------------------------
|
|
9
|
+
function extractHost(url) {
|
|
10
|
+
try {
|
|
11
|
+
return new URL(url).host;
|
|
12
|
+
}
|
|
13
|
+
catch {
|
|
14
|
+
return null;
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
function buildAllowedHosts(backendUrl, extra) {
|
|
18
|
+
const hosts = new Set();
|
|
19
|
+
// Always allow localhost variants
|
|
20
|
+
hosts.add('localhost');
|
|
21
|
+
hosts.add('127.0.0.1');
|
|
22
|
+
hosts.add('::1');
|
|
23
|
+
// Allow the configured backend
|
|
24
|
+
const backendHost = extractHost(backendUrl);
|
|
25
|
+
if (backendHost)
|
|
26
|
+
hosts.add(backendHost);
|
|
27
|
+
// Allow explicit extra origins
|
|
28
|
+
if (extra) {
|
|
29
|
+
for (const origin of extra) {
|
|
30
|
+
const h = extractHost(origin);
|
|
31
|
+
if (h)
|
|
32
|
+
hosts.add(h);
|
|
33
|
+
else
|
|
34
|
+
hosts.add(origin); // treat raw hostname as-is
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
return hosts;
|
|
38
|
+
}
|
|
39
|
+
function isAllowedOrigin(req, allowedHosts) {
|
|
40
|
+
// Determine the caller's host from multiple headers
|
|
41
|
+
// 1. Origin header (set by browsers and some HTTP clients)
|
|
42
|
+
const origin = req.headers.origin;
|
|
43
|
+
if (origin) {
|
|
44
|
+
const h = extractHost(origin);
|
|
45
|
+
return h !== null && isHostAllowed(h, allowedHosts);
|
|
46
|
+
}
|
|
47
|
+
// 2. Referer header fallback
|
|
48
|
+
const referer = req.headers.referer;
|
|
49
|
+
if (referer) {
|
|
50
|
+
const h = extractHost(referer);
|
|
51
|
+
return h !== null && isHostAllowed(h, allowedHosts);
|
|
52
|
+
}
|
|
53
|
+
// 3. X-Forwarded-For / remote IP — check if it's a local request
|
|
54
|
+
const remoteIp = req.ip ?? req.socket?.remoteAddress ?? '';
|
|
55
|
+
if (remoteIp === '127.0.0.1' || remoteIp === '::1' || remoteIp === '::ffff:127.0.0.1') {
|
|
56
|
+
return true;
|
|
57
|
+
}
|
|
58
|
+
// 4. No origin info — this is a direct server-to-server call.
|
|
59
|
+
// If API key auth is configured and passes, allow it.
|
|
60
|
+
// If no API key is configured, reject unknown-origin requests.
|
|
61
|
+
return false;
|
|
62
|
+
}
|
|
63
|
+
function isHostAllowed(host, allowedHosts) {
|
|
64
|
+
// Exact match (includes port, e.g. "localhost:4573")
|
|
65
|
+
if (allowedHosts.has(host))
|
|
66
|
+
return true;
|
|
67
|
+
// Match without port
|
|
68
|
+
const hostWithoutPort = host.split(':')[0];
|
|
69
|
+
if (allowedHosts.has(hostWithoutPort))
|
|
70
|
+
return true;
|
|
71
|
+
// Match subdomains (e.g. "api.elasticdash.com" matches "elasticdash.com")
|
|
72
|
+
for (const allowed of allowedHosts) {
|
|
73
|
+
if (hostWithoutPort.endsWith('.' + allowed))
|
|
74
|
+
return true;
|
|
75
|
+
}
|
|
76
|
+
return false;
|
|
77
|
+
}
|
|
78
|
+
// ---------------------------------------------------------------------------
|
|
79
|
+
// Server
|
|
80
|
+
// ---------------------------------------------------------------------------
|
|
81
|
+
export async function startPortalServer(options) {
|
|
82
|
+
const port = options.port ?? 4574;
|
|
83
|
+
const backendUrl = options.backendUrl.replace(/\/$/, '');
|
|
84
|
+
const apiKey = options.apiKey;
|
|
85
|
+
const cwd = options.cwd ?? process.cwd();
|
|
86
|
+
// Build allowed-origin set from backendUrl + explicit allowlist + localhost
|
|
87
|
+
const allowedHosts = buildAllowedHosts(backendUrl, options.allowedOrigins);
|
|
88
|
+
console.log(`[elasticdash portal] Allowed origins: ${[...allowedHosts].join(', ')}`);
|
|
89
|
+
// Scan tools at startup
|
|
90
|
+
const tools = scanTools(cwd);
|
|
91
|
+
console.log(`[elasticdash portal] Scanned ${tools.length} tools: ${tools.map(t => t.name).join(', ') || '(none)'}`);
|
|
92
|
+
// Queue state
|
|
93
|
+
const queue = [];
|
|
94
|
+
let processing = null;
|
|
95
|
+
let completed = 0;
|
|
96
|
+
let failed = 0;
|
|
97
|
+
let draining = false;
|
|
98
|
+
// -------------------------------------------------------------------------
|
|
99
|
+
// Queue processor
|
|
100
|
+
// -------------------------------------------------------------------------
|
|
101
|
+
async function processQueue() {
|
|
102
|
+
if (draining || processing)
|
|
103
|
+
return;
|
|
104
|
+
if (queue.length === 0)
|
|
105
|
+
return;
|
|
106
|
+
const task = queue.shift();
|
|
107
|
+
processing = task.taskId;
|
|
108
|
+
console.log(`[elasticdash portal] Processing task ${task.taskId} (type=${task.type}, name=${task.name}) — ${queue.length} remaining`);
|
|
109
|
+
const result = await executePortalTask(task, cwd, tools);
|
|
110
|
+
if (result.ok) {
|
|
111
|
+
completed++;
|
|
112
|
+
console.log(`[elasticdash portal] Task ${task.taskId} completed (${result.durationMs}ms)`);
|
|
113
|
+
}
|
|
114
|
+
else {
|
|
115
|
+
failed++;
|
|
116
|
+
console.log(`[elasticdash portal] Task ${task.taskId} failed: ${result.error}`);
|
|
117
|
+
}
|
|
118
|
+
processing = null;
|
|
119
|
+
// Deliver result to backend
|
|
120
|
+
await deliverResult(result);
|
|
121
|
+
// Process next task
|
|
122
|
+
processQueue().catch(() => { });
|
|
123
|
+
}
|
|
124
|
+
async function deliverResult(result) {
|
|
125
|
+
const url = `${backendUrl}/api/portal/results/${result.taskId}`;
|
|
126
|
+
const headers = { 'Content-Type': 'application/json' };
|
|
127
|
+
if (apiKey)
|
|
128
|
+
headers['Authorization'] = `Bearer ${apiKey}`;
|
|
129
|
+
for (let attempt = 0; attempt < 3; attempt++) {
|
|
130
|
+
try {
|
|
131
|
+
const res = await fetch(url, {
|
|
132
|
+
method: 'POST',
|
|
133
|
+
headers,
|
|
134
|
+
body: JSON.stringify(result),
|
|
135
|
+
});
|
|
136
|
+
if (res.ok || res.status < 500) {
|
|
137
|
+
debugLog(`[elasticdash portal] Result delivered for task ${result.taskId} (status ${res.status})`);
|
|
138
|
+
return;
|
|
139
|
+
}
|
|
140
|
+
debugLog(`[elasticdash portal] Result delivery failed (status ${res.status}), attempt ${attempt + 1}/3`);
|
|
141
|
+
}
|
|
142
|
+
catch (e) {
|
|
143
|
+
debugLog(`[elasticdash portal] Result delivery error, attempt ${attempt + 1}/3: ${e instanceof Error ? e.message : String(e)}`);
|
|
144
|
+
}
|
|
145
|
+
if (attempt < 2) {
|
|
146
|
+
await new Promise(r => setTimeout(r, Math.pow(2, attempt) * 1000));
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
console.warn(`[elasticdash portal] WARNING: Failed to deliver result for task ${result.taskId} after 3 retries`);
|
|
150
|
+
}
|
|
151
|
+
// -------------------------------------------------------------------------
|
|
152
|
+
// Security middleware: origin allowlist + API key auth
|
|
153
|
+
// -------------------------------------------------------------------------
|
|
154
|
+
function securityMiddleware(req, res, next) {
|
|
155
|
+
// Check API key first — a valid key overrides origin checks
|
|
156
|
+
if (apiKey) {
|
|
157
|
+
const authHeader = req.headers.authorization;
|
|
158
|
+
if (authHeader === `Bearer ${apiKey}`)
|
|
159
|
+
return next();
|
|
160
|
+
}
|
|
161
|
+
// Check origin allowlist
|
|
162
|
+
if (isAllowedOrigin(req, allowedHosts)) {
|
|
163
|
+
// If API key is configured but not provided, still reject
|
|
164
|
+
if (apiKey) {
|
|
165
|
+
console.warn(JSON.stringify({
|
|
166
|
+
event: 'auth.failure',
|
|
167
|
+
reason: 'invalid_api_key',
|
|
168
|
+
ip: req.ip || req.headers['x-forwarded-for'] || 'unknown',
|
|
169
|
+
userAgent: req.headers['user-agent'] || 'unknown',
|
|
170
|
+
timestamp: new Date().toISOString(),
|
|
171
|
+
}));
|
|
172
|
+
res.status(401).json({ ok: false, error: 'unauthorized — valid API key required' });
|
|
173
|
+
return;
|
|
174
|
+
}
|
|
175
|
+
return next();
|
|
176
|
+
}
|
|
177
|
+
// Origin not in allowlist and no valid API key
|
|
178
|
+
const origin = req.headers.origin ?? req.headers.referer ?? req.ip ?? 'unknown';
|
|
179
|
+
console.warn(JSON.stringify({
|
|
180
|
+
event: 'auth.failure',
|
|
181
|
+
reason: 'origin_not_allowed',
|
|
182
|
+
origin,
|
|
183
|
+
ip: req.ip || req.headers['x-forwarded-for'] || 'unknown',
|
|
184
|
+
userAgent: req.headers['user-agent'] || 'unknown',
|
|
185
|
+
timestamp: new Date().toISOString(),
|
|
186
|
+
}));
|
|
187
|
+
res.status(403).json({ ok: false, error: 'forbidden — origin not in allowlist' });
|
|
188
|
+
}
|
|
189
|
+
// -------------------------------------------------------------------------
|
|
190
|
+
// Express app
|
|
191
|
+
// -------------------------------------------------------------------------
|
|
192
|
+
const app = express();
|
|
193
|
+
app.use(express.json({ limit: '10mb' }));
|
|
194
|
+
// POST /api/portal/tasks — enqueue a single task
|
|
195
|
+
app.post('/api/portal/tasks', securityMiddleware, (req, res) => {
|
|
196
|
+
const task = req.body;
|
|
197
|
+
if (!task?.taskId || !task?.type || !task?.name) {
|
|
198
|
+
res.status(400).json({ ok: false, error: 'Missing required fields: taskId, type, name' });
|
|
199
|
+
return;
|
|
200
|
+
}
|
|
201
|
+
queue.push(task);
|
|
202
|
+
const position = queue.length;
|
|
203
|
+
debugLog(`[elasticdash portal] Task ${task.taskId} enqueued at position ${position}`);
|
|
204
|
+
res.status(202).json({ ok: true, taskId: task.taskId, position });
|
|
205
|
+
processQueue().catch(() => { });
|
|
206
|
+
});
|
|
207
|
+
// POST /api/portal/tasks/batch — enqueue multiple tasks
|
|
208
|
+
app.post('/api/portal/tasks/batch', securityMiddleware, (req, res) => {
|
|
209
|
+
const body = req.body;
|
|
210
|
+
if (!Array.isArray(body?.tasks) || body.tasks.length === 0) {
|
|
211
|
+
res.status(400).json({ ok: false, error: 'Missing or empty tasks array' });
|
|
212
|
+
return;
|
|
213
|
+
}
|
|
214
|
+
const results = [];
|
|
215
|
+
for (const task of body.tasks) {
|
|
216
|
+
if (!task?.taskId || !task?.type || !task?.name)
|
|
217
|
+
continue;
|
|
218
|
+
queue.push(task);
|
|
219
|
+
results.push({ taskId: task.taskId, position: queue.length });
|
|
220
|
+
}
|
|
221
|
+
debugLog(`[elasticdash portal] Batch enqueued ${results.length} tasks`);
|
|
222
|
+
res.status(202).json({ ok: true, tasks: results });
|
|
223
|
+
processQueue().catch(() => { });
|
|
224
|
+
});
|
|
225
|
+
// GET /api/portal/status — health check
|
|
226
|
+
app.get('/api/portal/status', (_req, res) => {
|
|
227
|
+
const status = {
|
|
228
|
+
ok: true,
|
|
229
|
+
queueLength: queue.length,
|
|
230
|
+
processing,
|
|
231
|
+
completed,
|
|
232
|
+
failed,
|
|
233
|
+
};
|
|
234
|
+
res.json(status);
|
|
235
|
+
});
|
|
236
|
+
// DELETE /api/portal/tasks/:taskId — cancel a pending task
|
|
237
|
+
app.delete('/api/portal/tasks/:taskId', securityMiddleware, (req, res) => {
|
|
238
|
+
const taskId = req.params.taskId;
|
|
239
|
+
const index = queue.findIndex(t => t.taskId === taskId);
|
|
240
|
+
if (index === -1) {
|
|
241
|
+
res.status(404).json({ ok: false, error: 'Task not found in queue (may be already processing or completed)' });
|
|
242
|
+
return;
|
|
243
|
+
}
|
|
244
|
+
queue.splice(index, 1);
|
|
245
|
+
res.json({ ok: true, taskId });
|
|
246
|
+
});
|
|
247
|
+
// -------------------------------------------------------------------------
|
|
248
|
+
// Start server
|
|
249
|
+
// -------------------------------------------------------------------------
|
|
250
|
+
const server = http.createServer(app);
|
|
251
|
+
await new Promise((resolve, reject) => {
|
|
252
|
+
server.on('error', reject);
|
|
253
|
+
server.listen(port, () => resolve());
|
|
254
|
+
});
|
|
255
|
+
const url = `http://localhost:${port}`;
|
|
256
|
+
return {
|
|
257
|
+
port,
|
|
258
|
+
url,
|
|
259
|
+
close: async () => {
|
|
260
|
+
draining = true;
|
|
261
|
+
// Wait for current task to finish if processing
|
|
262
|
+
if (processing) {
|
|
263
|
+
console.log(`[elasticdash portal] Waiting for current task ${processing} to finish...`);
|
|
264
|
+
await new Promise((resolve) => {
|
|
265
|
+
const check = setInterval(() => {
|
|
266
|
+
if (!processing) {
|
|
267
|
+
clearInterval(check);
|
|
268
|
+
resolve();
|
|
269
|
+
}
|
|
270
|
+
}, 200);
|
|
271
|
+
});
|
|
272
|
+
}
|
|
273
|
+
await new Promise((resolve, reject) => {
|
|
274
|
+
server.close((err) => err ? reject(err) : resolve());
|
|
275
|
+
});
|
|
276
|
+
},
|
|
277
|
+
};
|
|
278
|
+
}
|
|
279
|
+
//# sourceMappingURL=portal-server.js.map
|