@hivemagnus/magnus-server 0.0.0-stage → 1.0.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/.env.example +171 -0
- package/LICENSE +202 -0
- package/README.md +169 -2
- package/agent-version.json +1 -0
- package/dist/ai/HEARTBEAT.md +56 -0
- package/dist/ai/IDENTITY.md +54 -0
- package/dist/ai/MEMORY.md +40 -0
- package/dist/ai/TOOLS.md +538 -0
- package/dist/ai/USER.md +32 -0
- package/dist/cli/args.js +62 -0
- package/dist/cli/env-catalog.js +131 -0
- package/dist/cli/preflight.js +89 -0
- package/dist/cli.js +175 -0
- package/dist/config/cors.js +16 -0
- package/dist/config/env.js +392 -0
- package/dist/config/features.js +16 -0
- package/dist/config/index.js +3 -0
- package/dist/config/listener-security.js +38 -0
- package/dist/config/version.js +31 -0
- package/dist/db/client.js +44 -0
- package/dist/db/generated/prisma/browser.js +18 -0
- package/dist/db/generated/prisma/client.js +35 -0
- package/dist/db/generated/prisma/commonInputTypes.js +11 -0
- package/dist/db/generated/prisma/enums.js +42 -0
- package/dist/db/generated/prisma/internal/class.js +50 -0
- package/dist/db/generated/prisma/internal/prismaNamespace.js +1598 -0
- package/dist/db/generated/prisma/internal/prismaNamespaceBrowser.js +1569 -0
- package/dist/db/generated/prisma/models/A2ATask.js +2 -0
- package/dist/db/generated/prisma/models/AdmissionFence.js +2 -0
- package/dist/db/generated/prisma/models/Agent.js +2 -0
- package/dist/db/generated/prisma/models/AiConversation.js +2 -0
- package/dist/db/generated/prisma/models/AiDashboard.js +2 -0
- package/dist/db/generated/prisma/models/AiMemory.js +2 -0
- package/dist/db/generated/prisma/models/AiMessage.js +2 -0
- package/dist/db/generated/prisma/models/AiScheduledOp.js +2 -0
- package/dist/db/generated/prisma/models/AlertConditionState.js +2 -0
- package/dist/db/generated/prisma/models/AlertRule.js +2 -0
- package/dist/db/generated/prisma/models/AlertViolation.js +2 -0
- package/dist/db/generated/prisma/models/AnomalyEvent.js +2 -0
- package/dist/db/generated/prisma/models/ApplicationObservation.js +2 -0
- package/dist/db/generated/prisma/models/ArdentInstallation.js +2 -0
- package/dist/db/generated/prisma/models/ArdentProfile.js +2 -0
- package/dist/db/generated/prisma/models/ArdentScopeEnrollment.js +2 -0
- package/dist/db/generated/prisma/models/CommandResourceFence.js +2 -0
- package/dist/db/generated/prisma/models/CommandResourceLease.js +2 -0
- package/dist/db/generated/prisma/models/CommandResult.js +2 -0
- package/dist/db/generated/prisma/models/CronJob.js +2 -0
- package/dist/db/generated/prisma/models/CronJobExecution.js +2 -0
- package/dist/db/generated/prisma/models/DeployJob.js +2 -0
- package/dist/db/generated/prisma/models/DriftScan.js +2 -0
- package/dist/db/generated/prisma/models/EngineeringMetricSample.js +2 -0
- package/dist/db/generated/prisma/models/EngineeringRecoveryExercise.js +2 -0
- package/dist/db/generated/prisma/models/ExecutionRun.js +2 -0
- package/dist/db/generated/prisma/models/FilePatchTask.js +2 -0
- package/dist/db/generated/prisma/models/GpuSnapshot.js +2 -0
- package/dist/db/generated/prisma/models/HealthSnapshot.js +2 -0
- package/dist/db/generated/prisma/models/HermesCoreRollout.js +2 -0
- package/dist/db/generated/prisma/models/HermesCoreRolloutHost.js +2 -0
- package/dist/db/generated/prisma/models/HermesCredentialPool.js +2 -0
- package/dist/db/generated/prisma/models/HermesCredentialPoolAttachment.js +2 -0
- package/dist/db/generated/prisma/models/HermesCronJob.js +2 -0
- package/dist/db/generated/prisma/models/HermesHook.js +2 -0
- package/dist/db/generated/prisma/models/HermesInstallation.js +2 -0
- package/dist/db/generated/prisma/models/HermesMcpServer.js +2 -0
- package/dist/db/generated/prisma/models/HermesMcpServerAttachment.js +2 -0
- package/dist/db/generated/prisma/models/HermesMessagingBinding.js +2 -0
- package/dist/db/generated/prisma/models/HermesOperationJob.js +2 -0
- package/dist/db/generated/prisma/models/HermesPluginInstall.js +2 -0
- package/dist/db/generated/prisma/models/HermesProfile.js +2 -0
- package/dist/db/generated/prisma/models/HermesProviderConfig.js +2 -0
- package/dist/db/generated/prisma/models/HermesRegistryClient.js +2 -0
- package/dist/db/generated/prisma/models/HermesRegistryInventory.js +2 -0
- package/dist/db/generated/prisma/models/HermesRegistryReceipt.js +2 -0
- package/dist/db/generated/prisma/models/HermesRegistryRevision.js +2 -0
- package/dist/db/generated/prisma/models/HermesRegistryTarget.js +2 -0
- package/dist/db/generated/prisma/models/HermesRole.js +2 -0
- package/dist/db/generated/prisma/models/HermesRoleApplication.js +2 -0
- package/dist/db/generated/prisma/models/HermesSkillAssignment.js +2 -0
- package/dist/db/generated/prisma/models/HermesToolsetPolicy.js +2 -0
- package/dist/db/generated/prisma/models/IncidentLesson.js +2 -0
- package/dist/db/generated/prisma/models/LibraryCategory.js +2 -0
- package/dist/db/generated/prisma/models/LibraryFile.js +2 -0
- package/dist/db/generated/prisma/models/LogEntry.js +2 -0
- package/dist/db/generated/prisma/models/MagnusReleaseCanary.js +2 -0
- package/dist/db/generated/prisma/models/MaintenanceWindow.js +2 -0
- package/dist/db/generated/prisma/models/ManagedCronDefinition.js +2 -0
- package/dist/db/generated/prisma/models/ManagedCronOccurrence.js +2 -0
- package/dist/db/generated/prisma/models/Node.js +2 -0
- package/dist/db/generated/prisma/models/Notification.js +2 -0
- package/dist/db/generated/prisma/models/NotificationPreference.js +2 -0
- package/dist/db/generated/prisma/models/RemediationExecution.js +2 -0
- package/dist/db/generated/prisma/models/RemediationRule.js +2 -0
- package/dist/db/generated/prisma/models/RemoteCommand.js +2 -0
- package/dist/db/generated/prisma/models/ResourceDrainParticipant.js +2 -0
- package/dist/db/generated/prisma/models/ResourceEnrollment.js +2 -0
- package/dist/db/generated/prisma/models/ResourceParticipantWork.js +2 -0
- package/dist/db/generated/prisma/models/Role.js +2 -0
- package/dist/db/generated/prisma/models/RoleApplication.js +2 -0
- package/dist/db/generated/prisma/models/RolloutTarget.js +2 -0
- package/dist/db/generated/prisma/models/SecurityExportOutbox.js +2 -0
- package/dist/db/generated/prisma/models/SecurityFusionAction.js +2 -0
- package/dist/db/generated/prisma/models/SecurityFusionActionApproval.js +2 -0
- package/dist/db/generated/prisma/models/SecurityFusionCase.js +2 -0
- package/dist/db/generated/prisma/models/SecurityFusionCaseActivity.js +2 -0
- package/dist/db/generated/prisma/models/SecurityFusionCaseEntity.js +2 -0
- package/dist/db/generated/prisma/models/SecurityFusionCaseEvent.js +2 -0
- package/dist/db/generated/prisma/models/SecurityFusionCaseRelationship.js +2 -0
- package/dist/db/generated/prisma/models/SecurityFusionCollector.js +2 -0
- package/dist/db/generated/prisma/models/SecurityFusionCommandOutbox.js +2 -0
- package/dist/db/generated/prisma/models/SecurityFusionDeletionReceipt.js +2 -0
- package/dist/db/generated/prisma/models/SecurityFusionDelivery.js +2 -0
- package/dist/db/generated/prisma/models/SecurityFusionEntity.js +2 -0
- package/dist/db/generated/prisma/models/SecurityFusionEntityAlias.js +2 -0
- package/dist/db/generated/prisma/models/SecurityFusionEvent.js +2 -0
- package/dist/db/generated/prisma/models/SecurityFusionEventEntity.js +2 -0
- package/dist/db/generated/prisma/models/SecurityFusionEvidence.js +2 -0
- package/dist/db/generated/prisma/models/SecurityFusionInvestigation.js +2 -0
- package/dist/db/generated/prisma/models/SecurityFusionNonce.js +2 -0
- package/dist/db/generated/prisma/models/SecurityFusionReceiptChainHead.js +2 -0
- package/dist/db/generated/prisma/models/SecurityFusionRuleVersion.js +2 -0
- package/dist/db/generated/prisma/models/SecurityFusionSourceState.js +2 -0
- package/dist/db/generated/prisma/models/SecurityServiceState.js +2 -0
- package/dist/db/generated/prisma/models/SharedResourceFence.js +2 -0
- package/dist/db/generated/prisma/models/SharedResourceLease.js +2 -0
- package/dist/db/generated/prisma/models/SharedResourceLeaseResource.js +2 -0
- package/dist/db/generated/prisma/models/SkillInstallation.js +2 -0
- package/dist/db/generated/prisma/models/SkillRegistryEntry.js +2 -0
- package/dist/db/generated/prisma/models/SkillTag.js +2 -0
- package/dist/db/generated/prisma/models/SkillVersion.js +2 -0
- package/dist/db/generated/prisma/models/TeamApprovalDecision.js +2 -0
- package/dist/db/generated/prisma/models/User.js +2 -0
- package/dist/db/generated/prisma/models/WebhookDelivery.js +2 -0
- package/dist/db/generated/prisma/models/WebhookEndpoint.js +2 -0
- package/dist/db/generated/prisma/models.js +2 -0
- package/dist/db/index.js +3 -0
- package/dist/db/read-retry.js +35 -0
- package/dist/db/seed.js +348 -0
- package/dist/index.js +162 -0
- package/dist/middleware/auth.js +361 -0
- package/dist/middleware/body-limit.js +14 -0
- package/dist/middleware/correlation-id.js +13 -0
- package/dist/middleware/error-handler.js +28 -0
- package/dist/middleware/iam-token.js +21 -0
- package/dist/middleware/rate-limit.js +78 -0
- package/dist/middleware/request-logger.js +85 -0
- package/dist/middleware/security-headers.js +23 -0
- package/dist/middleware/tracing.js +120 -0
- package/dist/middleware/user-status.js +33 -0
- package/dist/openapi/spec.js +2656 -0
- package/dist/queue/redis.js +20 -0
- package/dist/queue/requeue.js +27 -0
- package/dist/queue/workers.js +342 -0
- package/dist/routes/ai-schedule.js +73 -0
- package/dist/routes/alerts.js +79 -0
- package/dist/routes/anomalies.js +101 -0
- package/dist/routes/ardent.js +82 -0
- package/dist/routes/assistant.js +417 -0
- package/dist/routes/audit.js +138 -0
- package/dist/routes/codex.js +65 -0
- package/dist/routes/commands.js +226 -0
- package/dist/routes/credentials.js +58 -0
- package/dist/routes/cron.js +292 -0
- package/dist/routes/dependencies.js +49 -0
- package/dist/routes/deploy.js +18 -0
- package/dist/routes/discovery.js +168 -0
- package/dist/routes/docs.js +24 -0
- package/dist/routes/drift.js +126 -0
- package/dist/routes/events-stream.js +87 -0
- package/dist/routes/files.js +214 -0
- package/dist/routes/fleet-checks.js +21 -0
- package/dist/routes/health.js +133 -0
- package/dist/routes/heartbeat.js +545 -0
- package/dist/routes/hermes-registry.js +278 -0
- package/dist/routes/hermes.js +61 -0
- package/dist/routes/library.js +402 -0
- package/dist/routes/logs.js +172 -0
- package/dist/routes/managed-cron.js +23 -0
- package/dist/routes/mcp.js +39 -0
- package/dist/routes/metrics.js +187 -0
- package/dist/routes/nodes.js +951 -0
- package/dist/routes/notifications.js +130 -0
- package/dist/routes/ops.js +258 -0
- package/dist/routes/orchestra.js +430 -0
- package/dist/routes/predictions.js +23 -0
- package/dist/routes/profiling.js +68 -0
- package/dist/routes/quick-command.js +72 -0
- package/dist/routes/remediation.js +111 -0
- package/dist/routes/resource-leases.js +297 -0
- package/dist/routes/roles.js +370 -0
- package/dist/routes/rollout.js +124 -0
- package/dist/routes/scan.js +116 -0
- package/dist/routes/security-backup.js +58 -0
- package/dist/routes/security-fusion.js +605 -0
- package/dist/routes/security-integration.js +71 -0
- package/dist/routes/self-update.js +129 -0
- package/dist/routes/shared-resource-leases.js +78 -0
- package/dist/routes/skills.js +485 -0
- package/dist/routes/stats.js +96 -0
- package/dist/routes/team-approvals.js +23 -0
- package/dist/routes/terminal-ticket.js +15 -0
- package/dist/routes/terminal.js +17 -0
- package/dist/routes/topology.js +37 -0
- package/dist/routes/users.js +219 -0
- package/dist/routes/webhooks.js +137 -0
- package/dist/schemas/command-reconciliation.js +14 -0
- package/dist/schemas/commands.js +51 -0
- package/dist/schemas/cron.js +33 -0
- package/dist/schemas/deploy.js +20 -0
- package/dist/schemas/files.js +14 -0
- package/dist/schemas/hermes.js +69 -0
- package/dist/schemas/index.js +11 -0
- package/dist/schemas/library.js +48 -0
- package/dist/schemas/logs.js +35 -0
- package/dist/schemas/managed-cron.js +127 -0
- package/dist/schemas/node-responses.js +87 -0
- package/dist/schemas/node-session-receipt.js +84 -0
- package/dist/schemas/nodes.js +36 -0
- package/dist/schemas/orchestra.js +118 -0
- package/dist/schemas/resource-participant-reconciliation.js +99 -0
- package/dist/schemas/roles.js +54 -0
- package/dist/schemas/shared-resource-lease.js +132 -0
- package/dist/schemas/skills.js +44 -0
- package/dist/schemas/users.js +28 -0
- package/dist/scripts/run-disposable-recovery-exercise.js +39 -0
- package/dist/sdk/index.js +2 -0
- package/dist/sdk/node-api-client.js +474 -0
- package/dist/security/approval-assertion.js +66 -0
- package/dist/security/command-completion-registry.js +35 -0
- package/dist/security/command-request.js +12 -0
- package/dist/security/command-result-signature.js +34 -0
- package/dist/security/managed-http.js +45 -0
- package/dist/security/native-completion-registry.js +44 -0
- package/dist/server.js +178 -0
- package/dist/services/admission-fence.js +71 -0
- package/dist/services/ai-assistant-service.js +2523 -0
- package/dist/services/ai-correlation-service.js +225 -0
- package/dist/services/ai-export-service.js +141 -0
- package/dist/services/ai-insights-service.js +219 -0
- package/dist/services/ai-schedule-service.js +145 -0
- package/dist/services/ai-scratchpad-service.js +222 -0
- package/dist/services/ai-tools.js +1343 -0
- package/dist/services/alert-service.js +313 -0
- package/dist/services/anomaly-detector.js +419 -0
- package/dist/services/api-local-work.js +79 -0
- package/dist/services/application-probes.js +309 -0
- package/dist/services/ardent-scope-enrollment.js +123 -0
- package/dist/services/ardent-service.js +86 -0
- package/dist/services/audit-service.js +308 -0
- package/dist/services/codex-service.js +209 -0
- package/dist/services/command-lifecycle.js +60 -0
- package/dist/services/command-resource-leases.js +305 -0
- package/dist/services/control-host-node.js +242 -0
- package/dist/services/credential-rotation-service.js +294 -0
- package/dist/services/dependency-service.js +145 -0
- package/dist/services/disposable-recovery-runner.js +595 -0
- package/dist/services/docker-group-health.js +121 -0
- package/dist/services/engineering-assurance.js +492 -0
- package/dist/services/engineering-evidence-ledger.js +94 -0
- package/dist/services/engineering-metric-evidence.js +26 -0
- package/dist/services/event-bus.js +227 -0
- package/dist/services/fleet-check-log.js +102 -0
- package/dist/services/forecast-service.js +154 -0
- package/dist/services/health-retention.js +175 -0
- package/dist/services/heartbeat-stream.js +63 -0
- package/dist/services/hermes-catalog-import.js +62 -0
- package/dist/services/hermes-cli-parser.js +671 -0
- package/dist/services/hermes-core-rollout.js +90 -0
- package/dist/services/hermes-dashboard-endpoint.js +13 -0
- package/dist/services/hermes-plugin-source.js +149 -0
- package/dist/services/hermes-registry-service.js +258 -0
- package/dist/services/hermes-service.js +566 -0
- package/dist/services/hivemem-client.js +85 -0
- package/dist/services/host-local-writer.js +82 -0
- package/dist/services/incident-evidence.js +110 -0
- package/dist/services/incident-lessons.js +186 -0
- package/dist/services/llm-providers/anthropic.js +143 -0
- package/dist/services/llm-providers/index.js +7 -0
- package/dist/services/llm-providers/llama-cpp.js +237 -0
- package/dist/services/llm-providers/ollama.js +292 -0
- package/dist/services/llm-providers/openai-compatible.js +200 -0
- package/dist/services/llm-providers/provider-error.js +36 -0
- package/dist/services/llm-providers/registry.js +45 -0
- package/dist/services/llm-providers/types.js +2 -0
- package/dist/services/log-emitter.js +19 -0
- package/dist/services/log-retention.js +66 -0
- package/dist/services/maintenance-service.js +29 -0
- package/dist/services/managed-cron.js +493 -0
- package/dist/services/managed-ssh-admission.js +68 -0
- package/dist/services/monitor-pulse.js +10 -0
- package/dist/services/node-issue-monitor.js +71 -0
- package/dist/services/node-service.js +35 -0
- package/dist/services/notification-retention.js +51 -0
- package/dist/services/notification-service.js +94 -0
- package/dist/services/operational-observations.js +112 -0
- package/dist/services/operations-config.js +67 -0
- package/dist/services/ops-service.js +491 -0
- package/dist/services/orchestra-extras-service.js +370 -0
- package/dist/services/orchestra-resources-service.js +530 -0
- package/dist/services/os-scheduler-writer.js +73 -0
- package/dist/services/password-service.js +22 -0
- package/dist/services/prediction-service.js +284 -0
- package/dist/services/recovery-exercises.js +682 -0
- package/dist/services/remediation-service.js +325 -0
- package/dist/services/resource-enrollment.js +433 -0
- package/dist/services/resource-node-lock.js +18 -0
- package/dist/services/resource-participant-work.js +302 -0
- package/dist/services/role-service.js +388 -0
- package/dist/services/rollout-service.js +440 -0
- package/dist/services/security-export.js +185 -0
- package/dist/services/security-fusion-actions.js +22 -0
- package/dist/services/security-fusion-approvals.js +83 -0
- package/dist/services/security-fusion-evidence-integrity.js +144 -0
- package/dist/services/security-fusion-ingest.js +101 -0
- package/dist/services/security-fusion-outbox.js +185 -0
- package/dist/services/security-fusion-receipt-chain.js +134 -0
- package/dist/services/security-fusion-service.js +751 -0
- package/dist/services/security-fusion-verification.js +50 -0
- package/dist/services/self-update-service.js +200 -0
- package/dist/services/shared-resource-leases.js +413 -0
- package/dist/services/skill-publication-bundle.js +90 -0
- package/dist/services/skill-publication-settlement.js +36 -0
- package/dist/services/skill-publication-state.js +40 -0
- package/dist/services/skill-registry-service.js +327 -0
- package/dist/services/skill-service.js +148 -0
- package/dist/services/ssh-exec-service.js +139 -0
- package/dist/services/ssh-host-verification.js +27 -0
- package/dist/services/tailscale-node-key-service.js +75 -0
- package/dist/services/team-approvals.js +118 -0
- package/dist/services/topology-service.js +138 -0
- package/dist/services/transfer-authority-snapshot.js +151 -0
- package/dist/services/webhook-credential-migration.js +97 -0
- package/dist/services/webhook-security.js +242 -0
- package/dist/services/webhook-service.js +494 -0
- package/dist/types/auth.js +3 -0
- package/dist/types/index.js +3 -0
- package/dist/types/library.js +3 -0
- package/dist/types/notifications.js +3 -0
- package/dist/types/roles.js +3 -0
- package/dist/types/shared-contracts.js +3 -0
- package/dist/utils/env.js +141 -0
- package/dist/utils/heartbeat-peer.js +19 -0
- package/dist/utils/idempotency.js +45 -0
- package/dist/utils/ip.js +25 -0
- package/dist/utils/json-fields.js +94 -0
- package/dist/utils/local-tls.js +39 -0
- package/dist/utils/magnus-home.js +15 -0
- package/dist/utils/node-url.js +59 -0
- package/dist/utils/redact.js +26 -0
- package/dist/utils/safe-equal.js +13 -0
- package/dist/utils/sentinel-nodes.js +16 -0
- package/dist/utils/validate.js +41 -0
- package/package.json +119 -4
- package/prisma/migrations/20261031000000_baseline/migration.sql +2935 -0
- package/prisma/migrations/migration_lock.toml +3 -0
- package/prisma/schema.prisma +2185 -0
- package/prisma.config.ts +14 -0
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
/** Fresh, owner-signed proof of the node agent's one-way mutation drain.
|
|
2
|
+
* A proof is only one independent writer class; it cannot transfer release
|
|
3
|
+
* traffic or settle a retained unknown claim. */
|
|
4
|
+
import { createHash, createHmac, timingSafeEqual } from 'node:crypto';
|
|
5
|
+
import { z } from 'zod';
|
|
6
|
+
import { canonical } from './resource-enrollment.js';
|
|
7
|
+
import { getNodeBaseUrl } from '../utils/node-url.js';
|
|
8
|
+
import { getNodeManagerSecret, RESULT_SIGNING_SECRET } from '../utils/env.js';
|
|
9
|
+
const hex = z.string().regex(/^[a-f0-9]{64}$/);
|
|
10
|
+
const proofSchema = z.object({ version: z.literal(1), sourceClass: z.literal('host-local-mutations'),
|
|
11
|
+
nodeId: z.string().min(1).max(128), nodeFqdn: z.string().min(1).max(253),
|
|
12
|
+
epoch: z.number().int().positive(), barrierId: z.string().guid(), nonce: z.string().guid(),
|
|
13
|
+
observedAt: z.number().int().positive(), active: z.number().int().nonnegative(),
|
|
14
|
+
unknown: z.number().int().nonnegative(), mutationInventorySha256: hex,
|
|
15
|
+
admissionFenceSha256: hex, signature: hex }).strict();
|
|
16
|
+
const DOMAIN = 'magnus.host-local-writer.v1\n';
|
|
17
|
+
export function verifyHostLocalWriterProof(raw, expected, now = Date.now()) {
|
|
18
|
+
const parsed = proofSchema.safeParse(raw);
|
|
19
|
+
if (!parsed.success || RESULT_SIGNING_SECRET.length < 32)
|
|
20
|
+
return null;
|
|
21
|
+
const { signature, ...receipt } = parsed.data;
|
|
22
|
+
if (receipt.nodeId !== expected.nodeId || receipt.nodeFqdn !== expected.nodeFqdn
|
|
23
|
+
|| receipt.nonce !== expected.nonce || receipt.epoch !== expected.epoch
|
|
24
|
+
|| receipt.barrierId !== expected.barrierId || receipt.active !== 0 || receipt.unknown !== 0
|
|
25
|
+
|| now - receipt.observedAt < -1000 || now - receipt.observedAt > 20_000)
|
|
26
|
+
return null;
|
|
27
|
+
const exactFence = createHash('sha256').update(canonical({ nodeId: expected.nodeId,
|
|
28
|
+
nodeFqdn: expected.nodeFqdn, epoch: expected.epoch, barrierId: expected.barrierId })).digest('hex');
|
|
29
|
+
if (receipt.admissionFenceSha256 !== exactFence)
|
|
30
|
+
return null;
|
|
31
|
+
const signed = createHmac('sha256', RESULT_SIGNING_SECRET).update(DOMAIN + canonical(receipt)).digest();
|
|
32
|
+
if (!timingSafeEqual(signed, Buffer.from(signature, 'hex')))
|
|
33
|
+
return null;
|
|
34
|
+
return { ownerReceiptSha256: createHash('sha256').update(canonical(parsed.data)).digest('hex'),
|
|
35
|
+
admissionFenceSha256: receipt.admissionFenceSha256,
|
|
36
|
+
mutationInventorySha256: receipt.mutationInventorySha256 };
|
|
37
|
+
}
|
|
38
|
+
async function boundedJson(response) {
|
|
39
|
+
if (!response.ok || !response.body)
|
|
40
|
+
throw new Error('Owner proof unavailable');
|
|
41
|
+
const reader = response.body.getReader();
|
|
42
|
+
const parts = [];
|
|
43
|
+
let size = 0;
|
|
44
|
+
try {
|
|
45
|
+
while (true) {
|
|
46
|
+
const { value, done } = await reader.read();
|
|
47
|
+
if (done)
|
|
48
|
+
break;
|
|
49
|
+
size += value.byteLength;
|
|
50
|
+
if (size > 4096)
|
|
51
|
+
throw new Error('Owner proof exceeded bound');
|
|
52
|
+
parts.push(value);
|
|
53
|
+
}
|
|
54
|
+
const envelope = JSON.parse(Buffer.concat(parts).toString('utf8'));
|
|
55
|
+
if (envelope.success !== true)
|
|
56
|
+
throw new Error('Owner proof rejected');
|
|
57
|
+
return envelope.data;
|
|
58
|
+
}
|
|
59
|
+
finally {
|
|
60
|
+
await reader.cancel().catch(() => undefined);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
export async function fetchHostLocalWriterProof(node, nodeFqdn, nonce) {
|
|
64
|
+
try {
|
|
65
|
+
if (RESULT_SIGNING_SECRET.length < 32)
|
|
66
|
+
return null;
|
|
67
|
+
const base = getNodeBaseUrl(node.ipAddress, node.port);
|
|
68
|
+
if (new URL(base).protocol !== 'https:')
|
|
69
|
+
return null;
|
|
70
|
+
const url = new URL(base + '/api/v1/commands/host-local-writer-proof');
|
|
71
|
+
url.search = new URLSearchParams({ nodeFqdn, nonce }).toString();
|
|
72
|
+
const response = await fetch(url, {
|
|
73
|
+
method: 'GET', redirect: 'error', signal: AbortSignal.timeout(5000),
|
|
74
|
+
headers: { 'x-node-token': getNodeManagerSecret(), 'cache-control': 'no-store' },
|
|
75
|
+
});
|
|
76
|
+
return await boundedJson(response);
|
|
77
|
+
}
|
|
78
|
+
catch {
|
|
79
|
+
return null;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
//# sourceMappingURL=host-local-writer.js.map
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
import { readFileSync, lstatSync } from 'node:fs';
|
|
2
|
+
import { resolve } from 'node:path';
|
|
3
|
+
import { createHash } from 'node:crypto';
|
|
4
|
+
import { getDb } from '../db/index.js';
|
|
5
|
+
import { HttpError } from '../middleware/error-handler.js';
|
|
6
|
+
export function evidenceEnvelope(value, maxBytes = 100000) {
|
|
7
|
+
const json = JSON.stringify(value, null, 2);
|
|
8
|
+
if (Buffer.byteLength(json) > maxBytes)
|
|
9
|
+
throw new HttpError(413, 'EVIDENCE_LIMIT', 'Narrow the incident evidence request');
|
|
10
|
+
return {
|
|
11
|
+
schemaVersion: 1,
|
|
12
|
+
sha256: createHash('sha256').update(json).digest('hex'),
|
|
13
|
+
bytes: Buffer.byteLength(json),
|
|
14
|
+
json,
|
|
15
|
+
markdown: `# Magnus incident evidence\n\nIncident: ${String(value.incidentId)}\n\nSHA-256: ${createHash('sha256').update(json).digest('hex')}\n\nThis packet records observations and ordering, not causality. Command text, stdout, logs, credentials, and private case content are excluded.\n\n\`\`\`json\n${json}\n\`\`\`\n`,
|
|
16
|
+
};
|
|
17
|
+
}
|
|
18
|
+
export function currentReleaseEvidence(file = resolve(import.meta.dirname, '../../release-manifest.json')) {
|
|
19
|
+
try {
|
|
20
|
+
const stat = lstatSync(file);
|
|
21
|
+
if (!stat.isFile() || stat.isSymbolicLink() || stat.size > 32768)
|
|
22
|
+
return null;
|
|
23
|
+
const value = JSON.parse(readFileSync(file, 'utf8'));
|
|
24
|
+
const keys = ['commit', 'sourceTreeSha256', 'releaseTreeSha256', 'distTreeSha256', 'prismaSchemaSha256'];
|
|
25
|
+
const result = {};
|
|
26
|
+
for (const key of keys) {
|
|
27
|
+
const field = value[key];
|
|
28
|
+
if (typeof field !== 'string' || !(key === 'commit' ? /^[a-f0-9]{40}$/ : /^[a-f0-9]{64}$/).test(field))
|
|
29
|
+
return null;
|
|
30
|
+
result[key] = field;
|
|
31
|
+
}
|
|
32
|
+
return { ...result, context: 'Current Control API release; not proof of the release active during this incident' };
|
|
33
|
+
}
|
|
34
|
+
catch {
|
|
35
|
+
return null;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
export async function exportIncidentEvidence(id) {
|
|
39
|
+
const db = getDb();
|
|
40
|
+
const incident = await db.anomalyEvent.findUnique({ where: { id } });
|
|
41
|
+
if (!incident)
|
|
42
|
+
throw new HttpError(404, 'NOT_FOUND', 'Incident not found');
|
|
43
|
+
const start = new Date(incident.firstSeenAt.getTime() - 3600000);
|
|
44
|
+
const end = new Date((incident.resolvedAt ?? incident.lastSeenAt).getTime() + 3600000);
|
|
45
|
+
const [health, commands, targets] = await Promise.all([
|
|
46
|
+
db.healthSnapshot.findMany({
|
|
47
|
+
where: { nodeId: incident.nodeId, timestamp: { gte: start, lte: end } },
|
|
48
|
+
take: 20,
|
|
49
|
+
orderBy: [{ timestamp: 'asc' }, { id: 'asc' }],
|
|
50
|
+
select: {
|
|
51
|
+
id: true,
|
|
52
|
+
timestamp: true,
|
|
53
|
+
cpuUsagePercent: true,
|
|
54
|
+
memoryPercent: true,
|
|
55
|
+
source: true,
|
|
56
|
+
},
|
|
57
|
+
}),
|
|
58
|
+
db.remoteCommand.findMany({
|
|
59
|
+
where: { nodeId: incident.nodeId, createdAt: { gte: start, lte: end } },
|
|
60
|
+
take: 20,
|
|
61
|
+
orderBy: [{ createdAt: 'asc' }, { id: 'asc' }],
|
|
62
|
+
select: {
|
|
63
|
+
id: true,
|
|
64
|
+
status: true,
|
|
65
|
+
createdAt: true,
|
|
66
|
+
completedAt: true,
|
|
67
|
+
correlationId: true,
|
|
68
|
+
result: { select: { exitCode: true } },
|
|
69
|
+
},
|
|
70
|
+
}),
|
|
71
|
+
db.rolloutTarget.findMany({
|
|
72
|
+
where: { nodeId: incident.nodeId, startedAt: { gte: start, lte: end } },
|
|
73
|
+
take: 20,
|
|
74
|
+
orderBy: [{ startedAt: 'asc' }, { id: 'asc' }],
|
|
75
|
+
select: { id: true, rolloutId: true, status: true, commandId: true, evidence: true },
|
|
76
|
+
}),
|
|
77
|
+
]);
|
|
78
|
+
const release = currentReleaseEvidence();
|
|
79
|
+
return evidenceEnvelope({
|
|
80
|
+
incidentId: id,
|
|
81
|
+
nodeId: incident.nodeId,
|
|
82
|
+
incident,
|
|
83
|
+
window: { start, end },
|
|
84
|
+
health,
|
|
85
|
+
commands,
|
|
86
|
+
rollouts: targets.map((t) => {
|
|
87
|
+
let e = {};
|
|
88
|
+
try {
|
|
89
|
+
e = JSON.parse(t.evidence);
|
|
90
|
+
}
|
|
91
|
+
catch {
|
|
92
|
+
/* missing */
|
|
93
|
+
}
|
|
94
|
+
return {
|
|
95
|
+
id: t.id,
|
|
96
|
+
rolloutId: t.rolloutId,
|
|
97
|
+
status: t.status,
|
|
98
|
+
commandId: t.commandId,
|
|
99
|
+
heartbeatAt: e['heartbeatAt'] ?? null,
|
|
100
|
+
agentVersion: e['agentVersion'] ?? null,
|
|
101
|
+
verifiedAt: e['verifiedAt'] ?? null,
|
|
102
|
+
};
|
|
103
|
+
}),
|
|
104
|
+
limits: { health: 20, commands: 20, targets: 20, maxBytes: 100000 },
|
|
105
|
+
release,
|
|
106
|
+
missing: release ? ['Attach the historical release manifest if the incident preceded this deployment'] : ['Current release manifest unavailable; attach an independently verified deployment manifest'],
|
|
107
|
+
redaction: 'Allowlisted metadata only; no raw commands, outputs, logs, secrets or asserted cause',
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
//# sourceMappingURL=incident-evidence.js.map
|
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { getDb } from '../db/index.js';
|
|
3
|
+
import { HttpError } from '../middleware/error-handler.js';
|
|
4
|
+
import { boundedJson } from './application-probes.js';
|
|
5
|
+
const reviewedText = z
|
|
6
|
+
.string()
|
|
7
|
+
.trim()
|
|
8
|
+
.min(5)
|
|
9
|
+
.max(1200)
|
|
10
|
+
.refine((s) => !/(?:https?:\/\/|bearer\s|password\s*[:=]|token\s*[:=]|secret\s*[:=]|-----BEGIN|\beyJ[A-Za-z0-9_-]+\.)/i.test(s), 'Remove credentials, URLs, raw logs and private evidence from the reviewed text');
|
|
11
|
+
export const lessonSchema = z
|
|
12
|
+
.object({
|
|
13
|
+
incidentId: z.string().min(1).max(200),
|
|
14
|
+
symptom: reviewedText,
|
|
15
|
+
cause: reviewedText,
|
|
16
|
+
runbookVersion: z.enum(['diagnostics.v1', 'restart-service.v1']),
|
|
17
|
+
evidenceIds: z
|
|
18
|
+
.array(z.string().regex(/^[a-zA-Z0-9_-]{1,200}$/))
|
|
19
|
+
.min(1)
|
|
20
|
+
.max(20),
|
|
21
|
+
expiresAt: z.string().datetime(),
|
|
22
|
+
reviewed: z.literal(true),
|
|
23
|
+
})
|
|
24
|
+
.strict()
|
|
25
|
+
.refine((v) => Date.parse(v.expiresAt) > Date.now() && Date.parse(v.expiresAt) - Date.now() <= 90 * 86400000, 'Review expiry must be within 90 days');
|
|
26
|
+
function connection() {
|
|
27
|
+
const url = process.env['MAGNUS_LESSONS_HIVEMEM_URL'];
|
|
28
|
+
const token = process.env['MAGNUS_LESSONS_HIVEMEM_TOKEN'];
|
|
29
|
+
const workspace = process.env['MAGNUS_LESSONS_WORKSPACE'];
|
|
30
|
+
if (!url || !token || !workspace || workspace === 'default')
|
|
31
|
+
return null;
|
|
32
|
+
const base = new URL(url);
|
|
33
|
+
if (base.protocol !== 'https:' ||
|
|
34
|
+
base.username ||
|
|
35
|
+
base.password ||
|
|
36
|
+
base.search ||
|
|
37
|
+
base.hash ||
|
|
38
|
+
base.pathname !== '/')
|
|
39
|
+
throw new Error('Lesson memory requires a dedicated HTTPS origin');
|
|
40
|
+
if (token === process.env['HIVEMEM_TOKEN'])
|
|
41
|
+
throw new Error('Use a dedicated lesson credential, not the general memory token');
|
|
42
|
+
return { base, token, workspace };
|
|
43
|
+
}
|
|
44
|
+
async function request(path, method, body) {
|
|
45
|
+
const config = connection();
|
|
46
|
+
if (!config)
|
|
47
|
+
throw new HttpError(409, 'UNCONFIGURED', 'Configure a dedicated lesson workspace and non-admin credential');
|
|
48
|
+
const res = await fetch(new URL(path, config.base), {
|
|
49
|
+
method,
|
|
50
|
+
headers: { Authorization: `Bearer ${config.token}`, 'Content-Type': 'application/json' },
|
|
51
|
+
body: body ? JSON.stringify(body) : undefined,
|
|
52
|
+
redirect: 'error',
|
|
53
|
+
signal: AbortSignal.timeout(8000),
|
|
54
|
+
});
|
|
55
|
+
if (!res.ok) {
|
|
56
|
+
await res.body?.cancel();
|
|
57
|
+
throw new HttpError(502, 'LESSON_MEMORY_FAILED', 'Scoped lesson memory request failed');
|
|
58
|
+
}
|
|
59
|
+
return res.status === 204 ? {} : (await boundedJson(res));
|
|
60
|
+
}
|
|
61
|
+
export async function publishLesson(input, actorId) {
|
|
62
|
+
const db = getDb();
|
|
63
|
+
const incident = await db.anomalyEvent.findUnique({ where: { id: input.incidentId } });
|
|
64
|
+
if (!incident)
|
|
65
|
+
throw new HttpError(404, 'NOT_FOUND', 'Incident not found');
|
|
66
|
+
const evidence = await db.remediationExecution.findMany({
|
|
67
|
+
where: { id: { in: input.evidenceIds }, anomalyEventId: input.incidentId, status: 'completed' },
|
|
68
|
+
});
|
|
69
|
+
if (!evidence.length ||
|
|
70
|
+
!evidence.some((e) => {
|
|
71
|
+
try {
|
|
72
|
+
return JSON.parse(e.input).runbookVersion === input.runbookVersion;
|
|
73
|
+
}
|
|
74
|
+
catch {
|
|
75
|
+
return false;
|
|
76
|
+
}
|
|
77
|
+
}))
|
|
78
|
+
throw new HttpError(400, 'LESSON_EVIDENCE', 'Select a completed matching runbook execution for this incident');
|
|
79
|
+
const row = await db.incidentLesson.create({
|
|
80
|
+
data: {
|
|
81
|
+
incidentId: incident.id,
|
|
82
|
+
nodeId: incident.nodeId,
|
|
83
|
+
symptom: input.symptom,
|
|
84
|
+
cause: input.cause,
|
|
85
|
+
runbookVersion: input.runbookVersion,
|
|
86
|
+
evidenceIds: JSON.stringify(evidence.map((e) => e.id)),
|
|
87
|
+
reviewedBy: actorId,
|
|
88
|
+
expiresAt: new Date(input.expiresAt),
|
|
89
|
+
},
|
|
90
|
+
});
|
|
91
|
+
const config = connection();
|
|
92
|
+
if (!config)
|
|
93
|
+
return { ...row, publication: 'unconfigured' };
|
|
94
|
+
// One explicit attempt. Ambiguous transport failure retains the local review and
|
|
95
|
+
// is never automatically re-published, avoiding duplicate external memories.
|
|
96
|
+
try {
|
|
97
|
+
const memory = await request('/v1/memories', 'POST', {
|
|
98
|
+
content: JSON.stringify({
|
|
99
|
+
lessonId: row.id,
|
|
100
|
+
symptom: row.symptom,
|
|
101
|
+
cause: row.cause,
|
|
102
|
+
runbookVersion: row.runbookVersion,
|
|
103
|
+
evidenceIds: evidence.map((e) => e.id),
|
|
104
|
+
reviewedBy: actorId,
|
|
105
|
+
}),
|
|
106
|
+
type: 'procedural',
|
|
107
|
+
scope: 'private',
|
|
108
|
+
source: 'user_feedback',
|
|
109
|
+
workspace_id: config.workspace,
|
|
110
|
+
expires_at: input.expiresAt,
|
|
111
|
+
sensitivity: 'internal',
|
|
112
|
+
redaction_policy: 'redact',
|
|
113
|
+
tags: ['magnus-reviewed-lesson'],
|
|
114
|
+
metadata: { magnus_lesson_id: row.id },
|
|
115
|
+
});
|
|
116
|
+
if (typeof memory['id'] !== 'string' || !memory['id'])
|
|
117
|
+
throw new Error('Invalid memory receipt');
|
|
118
|
+
await db.incidentLesson.update({ where: { id: row.id }, data: { memoryId: memory['id'] } });
|
|
119
|
+
return { ...row, memoryId: memory['id'], publication: 'published' };
|
|
120
|
+
}
|
|
121
|
+
catch {
|
|
122
|
+
return {
|
|
123
|
+
...row,
|
|
124
|
+
publication: 'unknown',
|
|
125
|
+
message: 'Review the scoped memory receipt before any retry',
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
export async function lessonSuggestions(query) {
|
|
130
|
+
// The local approved ledger is the admission boundary. Expired, superseded,
|
|
131
|
+
// unrelated or model-invented memories cannot become execution authority.
|
|
132
|
+
const terms = query
|
|
133
|
+
.toLowerCase()
|
|
134
|
+
.split(/\W+/)
|
|
135
|
+
.filter((t) => t.length > 2)
|
|
136
|
+
.slice(0, 20);
|
|
137
|
+
const rows = await getDb().incidentLesson.findMany({
|
|
138
|
+
where: { supersededAt: null, expiresAt: { gt: new Date() } },
|
|
139
|
+
orderBy: { createdAt: 'desc' },
|
|
140
|
+
take: 200,
|
|
141
|
+
});
|
|
142
|
+
return {
|
|
143
|
+
advisoryOnly: true,
|
|
144
|
+
lessons: rows
|
|
145
|
+
.filter((r) => terms.some((t) => `${r.symptom} ${r.cause}`.toLowerCase().includes(t)))
|
|
146
|
+
.slice(0, 10),
|
|
147
|
+
};
|
|
148
|
+
}
|
|
149
|
+
export async function supersedeLesson(id) {
|
|
150
|
+
const row = await getDb().incidentLesson.update({
|
|
151
|
+
where: { id },
|
|
152
|
+
data: { supersededAt: new Date() },
|
|
153
|
+
});
|
|
154
|
+
let publication = 'invalidated-locally';
|
|
155
|
+
if (row.memoryId) {
|
|
156
|
+
try {
|
|
157
|
+
await request(`/v1/memories/${encodeURIComponent(row.memoryId)}`, 'DELETE');
|
|
158
|
+
publication = 'invalidated';
|
|
159
|
+
}
|
|
160
|
+
catch {
|
|
161
|
+
publication = 'remote-invalidation-pending';
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
return { id, publication };
|
|
165
|
+
}
|
|
166
|
+
export async function reconcileLessonInvalidations() {
|
|
167
|
+
if (!connection())
|
|
168
|
+
return;
|
|
169
|
+
const rows = await getDb().incidentLesson.findMany({
|
|
170
|
+
where: {
|
|
171
|
+
memoryId: { not: null },
|
|
172
|
+
OR: [{ supersededAt: { not: null } }, { expiresAt: { lte: new Date() } }],
|
|
173
|
+
},
|
|
174
|
+
take: 10,
|
|
175
|
+
});
|
|
176
|
+
for (const row of rows) {
|
|
177
|
+
try {
|
|
178
|
+
await request(`/v1/memories/${encodeURIComponent(row.memoryId)}`, 'DELETE');
|
|
179
|
+
await getDb().incidentLesson.update({ where: { id: row.id }, data: { memoryId: null } });
|
|
180
|
+
}
|
|
181
|
+
catch {
|
|
182
|
+
/* Retry deletion; never re-publish. */
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
//# sourceMappingURL=incident-lessons.js.map
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
import { createHttpProviderError, LlmProviderError, normalizeProviderError } from './provider-error.js';
|
|
2
|
+
function contentToString(content) {
|
|
3
|
+
if (typeof content === 'string')
|
|
4
|
+
return content;
|
|
5
|
+
if (Array.isArray(content)) {
|
|
6
|
+
return content
|
|
7
|
+
.map((entry) => {
|
|
8
|
+
if (!entry || typeof entry !== 'object')
|
|
9
|
+
return '';
|
|
10
|
+
if ('text' in entry)
|
|
11
|
+
return String(entry.text ?? '');
|
|
12
|
+
return '';
|
|
13
|
+
})
|
|
14
|
+
.join('');
|
|
15
|
+
}
|
|
16
|
+
return '';
|
|
17
|
+
}
|
|
18
|
+
export class AnthropicProvider {
|
|
19
|
+
baseUrl;
|
|
20
|
+
apiKey;
|
|
21
|
+
id = 'anthropic';
|
|
22
|
+
name = 'Anthropic';
|
|
23
|
+
supportsStreaming = false;
|
|
24
|
+
supportsToolCalling = false;
|
|
25
|
+
supportsStructuredOutputs = false;
|
|
26
|
+
constructor(baseUrl = process.env['ANTHROPIC_API_URL'] ?? 'https://api.anthropic.com/v1', apiKey = process.env['ANTHROPIC_API_KEY']) {
|
|
27
|
+
this.baseUrl = baseUrl;
|
|
28
|
+
this.apiKey = apiKey;
|
|
29
|
+
}
|
|
30
|
+
isConfigured() {
|
|
31
|
+
return Boolean(this.baseUrl && this.apiKey);
|
|
32
|
+
}
|
|
33
|
+
buildHeaders() {
|
|
34
|
+
return {
|
|
35
|
+
'Content-Type': 'application/json',
|
|
36
|
+
'x-api-key': this.apiKey ?? '',
|
|
37
|
+
'anthropic-version': '2023-06-01',
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
async chat(request) {
|
|
41
|
+
if (!this.isConfigured()) {
|
|
42
|
+
throw new LlmProviderError(this.id, 'configuration', 'Anthropic is not configured', false);
|
|
43
|
+
}
|
|
44
|
+
const system = request.messages
|
|
45
|
+
.filter((message) => message.role === 'system')
|
|
46
|
+
.map((message) => message.content)
|
|
47
|
+
.join('\n\n');
|
|
48
|
+
const messages = request.messages
|
|
49
|
+
.filter((message) => message.role !== 'system')
|
|
50
|
+
.map((message) => ({
|
|
51
|
+
role: message.role === 'assistant' ? 'assistant' : 'user',
|
|
52
|
+
content: message.content,
|
|
53
|
+
}));
|
|
54
|
+
try {
|
|
55
|
+
const response = await fetch(`${this.baseUrl.replace(/\/+$/, '')}/messages`, {
|
|
56
|
+
method: 'POST',
|
|
57
|
+
headers: this.buildHeaders(),
|
|
58
|
+
body: JSON.stringify({
|
|
59
|
+
model: request.model,
|
|
60
|
+
system,
|
|
61
|
+
max_tokens: request.maxTokens ?? 4096,
|
|
62
|
+
temperature: request.temperature ?? 0.3,
|
|
63
|
+
messages,
|
|
64
|
+
}),
|
|
65
|
+
signal: AbortSignal.timeout(120_000),
|
|
66
|
+
});
|
|
67
|
+
if (!response.ok) {
|
|
68
|
+
const text = await response.text().catch(() => '');
|
|
69
|
+
throw createHttpProviderError(this.id, response.status, `Anthropic API error: ${response.status} ${text.slice(0, 300)}`);
|
|
70
|
+
}
|
|
71
|
+
const data = (await response.json());
|
|
72
|
+
return {
|
|
73
|
+
providerId: this.id,
|
|
74
|
+
providerName: this.name,
|
|
75
|
+
model: data.model ?? request.model,
|
|
76
|
+
content: contentToString(data.content),
|
|
77
|
+
thinking: '',
|
|
78
|
+
evalCount: (data.usage?.input_tokens ?? 0) + (data.usage?.output_tokens ?? 0),
|
|
79
|
+
nativeToolCalls: [],
|
|
80
|
+
usedStructuredOutput: false,
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
catch (error) {
|
|
84
|
+
if (error instanceof LlmProviderError)
|
|
85
|
+
throw error;
|
|
86
|
+
throw normalizeProviderError(this.id, error);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
async healthCheck() {
|
|
90
|
+
const checkedAt = new Date().toISOString();
|
|
91
|
+
if (!this.isConfigured()) {
|
|
92
|
+
return {
|
|
93
|
+
id: this.id,
|
|
94
|
+
name: this.name,
|
|
95
|
+
configured: false,
|
|
96
|
+
healthy: false,
|
|
97
|
+
baseUrl: this.baseUrl,
|
|
98
|
+
error: 'Anthropic API URL or key is not configured',
|
|
99
|
+
checkedAt,
|
|
100
|
+
supportsStreaming: this.supportsStreaming,
|
|
101
|
+
supportsToolCalling: this.supportsToolCalling,
|
|
102
|
+
supportsStructuredOutputs: this.supportsStructuredOutputs,
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
try {
|
|
106
|
+
const response = await fetch(`${this.baseUrl.replace(/\/+$/, '')}/models`, {
|
|
107
|
+
headers: this.buildHeaders(),
|
|
108
|
+
signal: AbortSignal.timeout(10_000),
|
|
109
|
+
});
|
|
110
|
+
if (!response.ok) {
|
|
111
|
+
const text = await response.text().catch(() => '');
|
|
112
|
+
throw createHttpProviderError(this.id, response.status, `Anthropic health check failed: ${text.slice(0, 200)}`);
|
|
113
|
+
}
|
|
114
|
+
return {
|
|
115
|
+
id: this.id,
|
|
116
|
+
name: this.name,
|
|
117
|
+
configured: true,
|
|
118
|
+
healthy: true,
|
|
119
|
+
baseUrl: this.baseUrl,
|
|
120
|
+
checkedAt,
|
|
121
|
+
supportsStreaming: this.supportsStreaming,
|
|
122
|
+
supportsToolCalling: this.supportsToolCalling,
|
|
123
|
+
supportsStructuredOutputs: this.supportsStructuredOutputs,
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
catch (error) {
|
|
127
|
+
const normalized = normalizeProviderError(this.id, error);
|
|
128
|
+
return {
|
|
129
|
+
id: this.id,
|
|
130
|
+
name: this.name,
|
|
131
|
+
configured: true,
|
|
132
|
+
healthy: false,
|
|
133
|
+
baseUrl: this.baseUrl,
|
|
134
|
+
error: normalized.message,
|
|
135
|
+
checkedAt,
|
|
136
|
+
supportsStreaming: this.supportsStreaming,
|
|
137
|
+
supportsToolCalling: this.supportsToolCalling,
|
|
138
|
+
supportsStructuredOutputs: this.supportsStructuredOutputs,
|
|
139
|
+
};
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
//# sourceMappingURL=anthropic.js.map
|