@vellumai/assistant 0.11.1 → 0.11.2-staging.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/Dockerfile +1 -3
- package/README.md +1 -1
- package/eslint.config.mjs +28 -8
- package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/package.json +1 -0
- package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/src/__tests__/stripe-currency.test.ts +37 -0
- package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/src/stripe-currency.ts +55 -0
- package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/package.json +1 -0
- package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/src/__tests__/stripe-currency.test.ts +37 -0
- package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/src/stripe-currency.ts +55 -0
- package/node_modules/@vellumai/service-contracts/package.json +1 -0
- package/node_modules/@vellumai/service-contracts/src/__tests__/stripe-currency.test.ts +37 -0
- package/node_modules/@vellumai/service-contracts/src/stripe-currency.ts +55 -0
- package/node_modules/@vellumai/slack-text/src/index.test.ts +48 -0
- package/node_modules/@vellumai/slack-text/src/index.ts +25 -6
- package/openapi.yaml +292 -5
- package/package.json +1 -1
- package/src/__tests__/agent-loop-resume-interrupted.test.ts +223 -0
- package/src/__tests__/byok-default-profile-ensure.test.ts +43 -0
- package/src/__tests__/cli-logger-boundary-guard.test.ts +87 -0
- package/src/__tests__/compaction-events.test.ts +139 -2
- package/src/__tests__/config-loader-backfill.test.ts +35 -3
- package/src/__tests__/config-schema.test.ts +1 -0
- package/src/__tests__/conversation-agent-loop-fatal-cleanup.test.ts +30 -0
- package/src/__tests__/conversation-agent-loop.test.ts +180 -7
- package/src/__tests__/conversation-error.test.ts +211 -4
- package/src/__tests__/conversation-options-turn-scoped-transport.test.ts +64 -0
- package/src/__tests__/conversation-queue.test.ts +126 -0
- package/src/__tests__/conversation-retry-route.test.ts +46 -7
- package/src/__tests__/conversation-slash.test.ts +4 -2
- package/src/__tests__/conversation-summarize-route.test.ts +7 -1
- package/src/__tests__/conversation-summarize-up-to.test.ts +71 -5
- package/src/__tests__/document-create-dedupe.test.ts +2 -1
- package/src/__tests__/document-find-replace.test.ts +2 -1
- package/src/__tests__/document-tool-security.test.ts +2 -1
- package/src/__tests__/document-update-default-surface.test.ts +2 -1
- package/src/__tests__/document-workspace-file.test.ts +467 -0
- package/src/__tests__/emit-signal-routing-intent.test.ts +290 -49
- package/src/__tests__/guardian-card-withdrawal.test.ts +91 -3
- package/src/__tests__/http-user-message-parity.test.ts +66 -0
- package/src/__tests__/list-messages-attachments.test.ts +87 -0
- package/src/__tests__/list-messages-provider-error.test.ts +143 -0
- package/src/__tests__/list-messages-system-card.test.ts +100 -0
- package/src/__tests__/llm-resolver.test.ts +19 -9
- package/src/__tests__/managed-profile-guard.test.ts +23 -0
- package/src/__tests__/notification-platform-adapter.test.ts +130 -2
- package/src/__tests__/notification-telegram-adapter.test.ts +6 -0
- package/src/__tests__/notification-vellum-adapter.test.ts +45 -0
- package/src/__tests__/plugin-api-model-profiles.test.ts +10 -1
- package/src/__tests__/plugin-import-boundary-guard.test.ts +3 -0
- package/src/__tests__/provider-error-scenarios.test.ts +140 -0
- package/src/__tests__/provider-send-message-override-profile.test.ts +95 -0
- package/src/__tests__/run-conversation-turn-persistence.test.ts +66 -3
- package/src/__tests__/scripted-turn-metadata-persistence.test.ts +209 -0
- package/src/__tests__/skills.test.ts +27 -0
- package/src/__tests__/slack-channels-routes.test.ts +0 -2
- package/src/__tests__/slack-share-routes.test.ts +0 -3
- package/src/__tests__/slack-users-routes.test.ts +0 -2
- package/src/__tests__/subagent-tools.test.ts +11 -0
- package/src/__tests__/tool-preview-lifecycle.test.ts +58 -0
- package/src/__tests__/tool-result-spool.test.ts +5 -4
- package/src/__tests__/turn-boundary-resolution.test.ts +53 -0
- package/src/__tests__/turn-events-store.test.ts +26 -0
- package/src/__tests__/ui-visual-surface.test.ts +695 -0
- package/src/__tests__/unified-turn-context-visible-app.test.ts +99 -0
- package/src/__tests__/visible-app-context.test.ts +189 -0
- package/src/__tests__/workspace-git-service.test.ts +173 -33
- package/src/__tests__/workspace-migration-137-repair-retired-fireworks-minimax-model-id.test.ts +157 -0
- package/src/__tests__/workspace-migration-138-backfill-home-feed-titles.test.ts +373 -0
- package/src/__tests__/workspace-migration-139-clear-renamed-cost-profile-label.test.ts +137 -0
- package/src/agent/loop.ts +36 -7
- package/src/api/events/context-window-usage.ts +31 -0
- package/src/api/events/notification-intent.ts +8 -0
- package/src/api/events/ui-surface-pending.ts +35 -0
- package/src/api/index.ts +14 -0
- package/src/api/responses/conversation-message.ts +25 -4
- package/src/api/surfaces.ts +90 -1
- package/src/approvals/guardian-card-withdrawal.ts +66 -31
- package/src/approvals/guardian-decision-primitive.ts +4 -0
- package/src/calls/__tests__/call-setup-router.test.ts +156 -26
- package/src/calls/__tests__/voice-session-bridge.test.ts +201 -12
- package/src/calls/call-setup-router.ts +80 -36
- package/src/calls/voice-session-bridge.ts +173 -26
- package/src/cli/commands/inference.help.ts +3 -3
- package/src/cli/commands/notifications.help.ts +3 -2
- package/src/cli/commands/platform/__tests__/callback-routes-list.test.ts +42 -128
- package/src/cli/commands/platform/__tests__/credits.test.ts +9 -78
- package/src/cli/commands/platform/__tests__/helpers.ts +90 -0
- package/src/cli/commands/platform/__tests__/invoices.test.ts +238 -0
- package/src/cli/commands/platform/__tests__/plans.test.ts +9 -88
- package/src/cli/commands/platform/__tests__/status.test.ts +12 -87
- package/src/cli/commands/platform/__tests__/subscription.test.ts +9 -86
- package/src/cli/commands/platform/index.help.ts +92 -0
- package/src/cli/commands/platform/index.ts +7 -0
- package/src/cli/commands/platform/invoices.ts +132 -0
- package/src/cli/commands/usage.help.ts +1 -1
- package/src/cli/lib/list-installed-plugins.ts +2 -1
- package/src/config/__tests__/default-profile-catalog.test.ts +7 -10
- package/src/config/__tests__/deployment-context-defaults.test.ts +27 -5
- package/src/config/assistant-feature-flags.ts +7 -2
- package/src/config/bundled-skills/app-builder/SKILL.md +3 -2
- package/src/config/bundled-skills/subagent/SKILL.md +3 -1
- package/src/config/bundled-skills/subagent/TOOLS.json +3 -3
- package/src/config/bundled-skills/visualize/SKILL.md +163 -0
- package/src/config/call-site-defaults.ts +3 -2
- package/src/config/default-profile-catalog.ts +75 -70
- package/src/config/default-profile-names.ts +13 -28
- package/src/config/env-registry.ts +1 -0
- package/src/config/feature-flag-registry.json +16 -0
- package/src/config/llm-resolver.ts +21 -5
- package/src/config/loader.ts +18 -11
- package/src/config/schemas/llm.ts +1 -9
- package/src/config/schemas/memory-retrospective.ts +9 -0
- package/src/config/schemas/monitoring.ts +28 -2
- package/src/config/schemas/workspace-git.ts +15 -0
- package/src/config/seed-inference-profiles.ts +19 -5
- package/src/context/post-turn-tool-result-truncation.ts +2 -2
- package/src/conversations/__tests__/message-consolidation.test.ts +54 -0
- package/src/conversations/message-consolidation.ts +17 -15
- package/src/daemon/__tests__/turn-tail-assistant-reply-notify.test.ts +182 -0
- package/src/daemon/__tests__/turn-tail-deleted-conversation.test.ts +181 -0
- package/src/daemon/conversation-agent-loop-handlers.ts +90 -7
- package/src/daemon/conversation-agent-loop.ts +107 -22
- package/src/daemon/conversation-error.ts +169 -48
- package/src/daemon/conversation-messaging.ts +87 -4
- package/src/daemon/conversation-process.ts +57 -45
- package/src/daemon/conversation-runtime-assembly.ts +57 -0
- package/src/daemon/conversation-store.ts +36 -1
- package/src/daemon/conversation-surfaces.ts +68 -6
- package/src/daemon/conversation-turn-finalize.ts +76 -18
- package/src/daemon/conversation.ts +122 -27
- package/src/daemon/lifecycle.ts +9 -0
- package/src/daemon/message-types/conversations.ts +8 -0
- package/src/daemon/message-types/surfaces.ts +3 -0
- package/src/documents/document-store.ts +247 -10
- package/src/home/__tests__/feed-types.test.ts +43 -8
- package/src/home/__tests__/feed-writer.test.ts +59 -0
- package/src/home/feed-types.ts +34 -7
- package/src/home/feed-writer.ts +15 -6
- package/src/live-voice/__tests__/activity-label.test.ts +95 -0
- package/src/live-voice/__tests__/live-activity-reporter.test.ts +86 -5
- package/src/live-voice/__tests__/live-voice-agent-turn.test.ts +310 -25
- package/src/live-voice/__tests__/live-voice-events.test.ts +4 -4
- package/src/live-voice/__tests__/live-voice-triage-escalate.test.ts +8 -4
- package/src/live-voice/__tests__/live-voice-vad.test.ts +9 -1
- package/src/live-voice/activity-label.ts +169 -0
- package/src/live-voice/live-activity-reporter.ts +35 -6
- package/src/live-voice/live-voice-session.ts +282 -41
- package/src/live-voice/protocol.ts +35 -0
- package/src/messaging/providers/slack/__tests__/adapter-mention-rendering.test.ts +84 -2
- package/src/messaging/providers/slack/adapter.ts +86 -19
- package/src/messaging/providers/slack/api.test.ts +85 -1
- package/src/messaging/providers/slack/api.ts +121 -288
- package/src/messaging/providers/slack/client.ts +20 -251
- package/src/messaging/providers/slack/send.test.ts +4 -9
- package/src/messaging/providers/slack/send.ts +1 -1
- package/src/messaging/providers/slack/types.ts +5 -0
- package/src/messaging/providers/slack/web-api-transport.test.ts +181 -0
- package/src/messaging/providers/slack/web-api-transport.ts +367 -0
- package/src/messaging/providers/slack/withdraw.ts +6 -14
- package/src/messaging/providers/telegram-bot/send.test.ts +31 -1
- package/src/messaging/providers/telegram-bot/send.ts +23 -2
- package/src/messaging/providers/telegram-bot/withdraw.test.ts +152 -0
- package/src/messaging/providers/telegram-bot/withdraw.ts +166 -0
- package/src/monitoring/__tests__/db-integrity-sample.test.ts +18 -4
- package/src/monitoring/__tests__/file-descriptors.test.ts +144 -0
- package/src/monitoring/file-descriptors.ts +262 -0
- package/src/monitoring/process-memory.ts +4 -11
- package/src/monitoring/resource-sampler.ts +5 -0
- package/src/monitoring/worker.ts +12 -0
- package/src/notifications/__tests__/assistant-reply-producer.test.ts +740 -0
- package/src/notifications/__tests__/broadcaster.test.ts +347 -7
- package/src/notifications/__tests__/copy-composer.test.ts +53 -3
- package/src/notifications/__tests__/decision-engine.test.ts +349 -29
- package/src/notifications/__tests__/deterministic-checks.test.ts +21 -0
- package/src/notifications/__tests__/edit-notification.test.ts +330 -0
- package/src/notifications/__tests__/guardian-delivery-recorder.test.ts +71 -0
- package/src/notifications/__tests__/home-feed-side-effect.test.ts +170 -12
- package/src/notifications/adapters/macos.ts +3 -0
- package/src/notifications/adapters/platform.ts +90 -14
- package/src/notifications/adapters/telegram.ts +6 -4
- package/src/notifications/assistant-reply-producer.ts +219 -0
- package/src/notifications/broadcaster.ts +523 -317
- package/src/notifications/copy-composer.ts +27 -6
- package/src/notifications/decision-engine.ts +154 -77
- package/src/notifications/deterministic-checks.ts +6 -7
- package/src/notifications/edit-notification.ts +8 -4
- package/src/notifications/emit-signal.ts +49 -16
- package/src/notifications/guardian-delivery-recorder.ts +8 -2
- package/src/notifications/home-feed-side-effect.ts +43 -6
- package/src/notifications/notification-utils.ts +39 -4
- package/src/notifications/signal.ts +10 -0
- package/src/notifications/types.ts +17 -0
- package/src/persistence/bookmark-crud.ts +18 -9
- package/src/persistence/conversation-attention-store.ts +26 -0
- package/src/persistence/conversation-crud.ts +79 -32
- package/src/persistence/conversation-queries.ts +30 -15
- package/src/persistence/conversation-title-service.ts +5 -170
- package/src/persistence/conversation-types.ts +192 -1
- package/src/persistence/db-init.ts +14 -1
- package/src/persistence/db-maintenance.ts +5 -4
- package/src/persistence/embeddings/__tests__/plugin-index-qdrant-init.test.ts +219 -0
- package/src/persistence/embeddings/__tests__/plugin-index.test.ts +22 -0
- package/src/persistence/embeddings/__tests__/worker-script-version.test.ts +76 -0
- package/src/persistence/embeddings/embedding-local.ts +2 -1
- package/src/persistence/embeddings/embedding-runtime-manager.ts +29 -5
- package/src/persistence/embeddings/plugin-index.ts +84 -12
- package/src/persistence/migrations/360-add-document-workspace-path.test.ts +110 -0
- package/src/persistence/migrations/360-add-document-workspace-path.ts +39 -0
- package/src/persistence/planner-statistics.ts +14 -0
- package/src/persistence/schema/documents.ts +26 -11
- package/src/persistence/steps.ts +2 -0
- package/src/platform/client.test.ts +172 -2
- package/src/platform/client.ts +178 -55
- package/src/plugin-api/conversation-turn.ts +13 -0
- package/src/plugin-api/model-profiles.test.ts +6 -2
- package/src/plugins/defaults/memory/__tests__/bookmark-crud.test.ts +34 -0
- package/src/plugins/defaults/memory/__tests__/conversation-queries.test.ts +22 -0
- package/src/plugins/defaults/memory/__tests__/jobs-store-enqueue-gate.test.ts +11 -0
- package/src/plugins/defaults/memory/__tests__/memory-retrospective-accounting.test.ts +199 -0
- package/src/plugins/defaults/memory/__tests__/memory-retrospective-enqueue.test.ts +98 -1
- package/src/plugins/defaults/memory/__tests__/memory-retrospective-job.test.ts +105 -1
- package/src/plugins/defaults/memory/__tests__/memory-retrospective-sweep.test.ts +24 -2
- package/src/plugins/defaults/memory/__tests__/memory-retrospective-wake-chain.test.ts +524 -0
- package/src/plugins/defaults/memory/__tests__/memory-tier-boundary-guard.test.ts +1 -1
- package/src/plugins/defaults/memory/graph/__tests__/conversation-graph-memory-v2-routing.test.ts +9 -4
- package/src/plugins/defaults/memory/host-utils.ts +5 -0
- package/src/plugins/defaults/memory/memory-retrospective-accounting.ts +105 -1
- package/src/plugins/defaults/memory/memory-retrospective-enqueue.ts +64 -5
- package/src/plugins/defaults/memory/memory-retrospective-job.ts +36 -1
- package/src/plugins/defaults/memory/memory-retrospective-sweep.ts +12 -3
- package/src/plugins/defaults/memory/src/__tests__/memory-v2-simulate-route.test.ts +1 -0
- package/src/plugins/defaults/memory/substrate/__tests__/skill-store.test.ts +195 -1
- package/src/plugins/defaults/memory/substrate/consolidation-job.ts +1 -1
- package/src/plugins/defaults/memory/substrate/skill-store.ts +167 -29
- package/src/plugins/defaults/memory/v2/__tests__/injection.test.ts +131 -0
- package/src/plugins/defaults/memory/v2/__tests__/router.test.ts +1 -0
- package/src/plugins/defaults/memory/v2/activation-log-store.ts +4 -0
- package/src/plugins/defaults/memory/v2/injection.ts +48 -3
- package/src/plugins/defaults/memory/v2/rerank-local.ts +6 -2
- package/src/plugins/defaults/memory/v3/__tests__/pool-select.test.ts +1 -1
- package/src/plugins/defaults/platform-hosted/routes/reengage.ts +1 -1
- package/src/plugins/defaults/turn-context/injectors.ts +1 -0
- package/src/plugins/defaults/turn-context/unified-turn-context.ts +26 -0
- package/src/plugins/types.ts +17 -0
- package/src/prompts/templates/system-sections.ts +2 -2
- package/src/providers/__tests__/dispatch-connection-routing.test.ts +43 -2
- package/src/providers/__tests__/vellum-mismatch-routing.test.ts +8 -0
- package/src/providers/call-site-routing.ts +77 -14
- package/src/providers/connection-resolution.ts +42 -2
- package/src/providers/inference/__tests__/adapter-factory-openai-compatible.test.ts +127 -1
- package/src/providers/inference/adapter-factory.ts +70 -12
- package/src/providers/model-catalog.ts +0 -11
- package/src/providers/model-intents.ts +11 -0
- package/src/providers/registry.ts +8 -0
- package/src/providers/retry.ts +85 -17
- package/src/providers/types.ts +8 -1
- package/src/runtime/__tests__/agent-wake.test.ts +181 -0
- package/src/runtime/agent-wake.ts +132 -9
- package/src/runtime/channel-approval-types.ts +25 -0
- package/src/runtime/routes/__tests__/connection-routes-vs-cli-parity.test.ts +2 -2
- package/src/runtime/routes/__tests__/conversation-query-routes.test.ts +11 -0
- package/src/runtime/routes/__tests__/inference-provider-connection-routes.test.ts +121 -9
- package/src/runtime/routes/__tests__/platform-invoice-routes.test.ts +392 -0
- package/src/runtime/routes/__tests__/slack-channel-routes.test.ts +7 -0
- package/src/runtime/routes/__tests__/workspace-commit-routes.test.ts +63 -0
- package/src/runtime/routes/consolidation-routes.ts +1 -1
- package/src/runtime/routes/conversation-management-routes.ts +18 -6
- package/src/runtime/routes/conversation-query-routes.ts +14 -23
- package/src/runtime/routes/conversation-routes.ts +113 -8
- package/src/runtime/routes/credential-routes.ts +1 -5
- package/src/runtime/routes/documents-routes.ts +207 -12
- package/src/runtime/routes/identity-routes.ts +3 -93
- package/src/runtime/routes/inbound-stages/admission-policy.test.ts +31 -1
- package/src/runtime/routes/inbound-stages/admission-policy.ts +18 -0
- package/src/runtime/routes/inference-provider-connection-routes.ts +52 -3
- package/src/runtime/routes/notification-routes.ts +4 -1
- package/src/runtime/routes/platform-routes.ts +238 -3
- package/src/runtime/routes/playground/guard.ts +1 -2
- package/src/runtime/routes/slack-channel-routes.ts +2 -4
- package/src/runtime/routes/workspace-commit-routes.ts +49 -2
- package/src/runtime/routes/workspace-routes.ts +11 -12
- package/src/runtime/routes/workspace-utils.ts +49 -2
- package/src/runtime/services/conversation-serializer.ts +2 -4
- package/src/subagent/__tests__/consult-context-gating.test.ts +98 -0
- package/src/subagent/__tests__/consult-context-skills.test.ts +82 -0
- package/src/subagent/__tests__/consult-context.test.ts +84 -0
- package/src/subagent/__tests__/consult-prompt.test.ts +36 -0
- package/src/subagent/consult-context.ts +410 -0
- package/src/subagent/consult-prompt.ts +38 -5
- package/src/subagent/manager.ts +9 -4
- package/src/subagent/types.ts +8 -0
- package/src/telemetry/telemetry-event-sources.ts +7 -0
- package/src/telemetry/telemetry-wire-source.json +1 -1
- package/src/telemetry/telemetry-wire.generated.ts +1 -0
- package/src/telemetry/turn-events-store.ts +30 -1
- package/src/telemetry/types.ts +24 -0
- package/src/telemetry/usage-telemetry-reporter.test.ts +48 -0
- package/src/tools/browser/pinned-tabs.ts +3 -1
- package/src/tools/subagent/spawn.ts +23 -1
- package/src/tools/terminal/safe-env.ts +1 -0
- package/src/tools/ui-surface/definitions.ts +39 -1
- package/src/tools/ui-surface/surface-shape-docs.ts +38 -4
- package/src/tools/ui-surface/visual-validation.ts +787 -0
- package/src/tools/workflows/run-workflow.test.ts +1 -0
- package/src/util/__tests__/short-title.test.ts +229 -0
- package/src/util/__tests__/worker-compute.test.ts +65 -0
- package/src/util/cgroup-cpu.ts +93 -0
- package/src/util/errors.ts +27 -0
- package/src/util/process-tree.ts +19 -0
- package/src/util/short-title.ts +189 -0
- package/src/util/worker-compute.ts +83 -0
- package/src/workspace/byok-default-profile-ensure.ts +25 -15
- package/src/workspace/git-service.ts +79 -30
- package/src/workspace/migrations/137-repair-retired-fireworks-minimax-model-id.ts +131 -0
- package/src/workspace/migrations/138-backfill-home-feed-titles.ts +179 -0
- package/src/workspace/migrations/139-clear-renamed-cost-profile-label.ts +92 -0
- package/src/workspace/migrations/registry.ts +6 -0
- package/src/plugins/defaults/memory/substrate/constants.ts +0 -8
|
@@ -3,17 +3,18 @@
|
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
5
|
import { existsSync, readFileSync } from "node:fs";
|
|
6
|
-
import {
|
|
6
|
+
import { totalmem } from "node:os";
|
|
7
7
|
|
|
8
8
|
import { z } from "zod";
|
|
9
9
|
|
|
10
|
-
import {
|
|
10
|
+
import { getIsPlatform } from "../../config/env-registry.js";
|
|
11
11
|
import { getDbMigrationReadiness } from "../../daemon/daemon-readiness.js";
|
|
12
12
|
import { parseIdentityFields } from "../../daemon/handlers/identity.js";
|
|
13
13
|
import { getProfilerRuntimeStatus } from "../../daemon/profiler-run-store.js";
|
|
14
14
|
import { getMaxRollbackVersion } from "../../persistence/migrations/run-migrations.js";
|
|
15
15
|
import { migrationSteps } from "../../persistence/steps.js";
|
|
16
16
|
import { getCesClient } from "../../security/secure-keys.js";
|
|
17
|
+
import { getContainerCpuCores } from "../../util/cgroup-cpu.js";
|
|
17
18
|
import {
|
|
18
19
|
getContainerMemoryLimitBytes,
|
|
19
20
|
getContainerMemoryUsageBytes,
|
|
@@ -92,97 +93,6 @@ interface CpuInfo {
|
|
|
92
93
|
maxCores: number;
|
|
93
94
|
}
|
|
94
95
|
|
|
95
|
-
/**
|
|
96
|
-
* Parse a Kubernetes-style CPU string (e.g. "2000m", "1", "500m") into
|
|
97
|
-
* fractional cores. Returns null if the value is not a recognized format.
|
|
98
|
-
*/
|
|
99
|
-
function parseK8sCpuCores(value: string): number | null {
|
|
100
|
-
const trimmed = value.trim();
|
|
101
|
-
const milliMatch = trimmed.match(/^(\d+)m$/);
|
|
102
|
-
if (milliMatch) {
|
|
103
|
-
const millis = parseInt(milliMatch[1], 10);
|
|
104
|
-
return millis > 0 ? millis / 1000 : null;
|
|
105
|
-
}
|
|
106
|
-
if (/^\d+(\.\d+)?$/.test(trimmed)) {
|
|
107
|
-
const num = parseFloat(trimmed);
|
|
108
|
-
return !isNaN(num) && num > 0 ? num : null;
|
|
109
|
-
}
|
|
110
|
-
return null;
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
/**
|
|
114
|
-
* Read the container's CPU core limit.
|
|
115
|
-
*
|
|
116
|
-
* Resolution order:
|
|
117
|
-
* 1. VELLUM_CPU_LIMIT env var (K8s resource format, e.g. "2000m" or "2").
|
|
118
|
-
* In platform mode the container runs under gVisor where cgroup files may
|
|
119
|
-
* report the node's CPU count rather than the sandbox limit.
|
|
120
|
-
* 2. cgroups v2 cpu.max (quota / period → fractional cores).
|
|
121
|
-
* 3. cgroups v1 cpu.cfs_quota_us / cpu.cfs_period_us.
|
|
122
|
-
* 4. os.cpus().length as last resort.
|
|
123
|
-
*/
|
|
124
|
-
function getContainerCpuCores(): number {
|
|
125
|
-
// 1. Prefer the explicit env var set by the platform StatefulSet template.
|
|
126
|
-
try {
|
|
127
|
-
const envLimit = getCpuLimit();
|
|
128
|
-
if (envLimit) {
|
|
129
|
-
const parsed = parseK8sCpuCores(envLimit);
|
|
130
|
-
if (parsed !== null) {
|
|
131
|
-
return parsed;
|
|
132
|
-
}
|
|
133
|
-
}
|
|
134
|
-
} catch {
|
|
135
|
-
/* env var parsing failed – fall through */
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
// 2. Try cgroups v2: /sys/fs/cgroup/cpu.max contains "$MAX $PERIOD".
|
|
139
|
-
try {
|
|
140
|
-
const raw = readFileSync("/sys/fs/cgroup/cpu.max", "utf-8").trim();
|
|
141
|
-
if (!raw.startsWith("max")) {
|
|
142
|
-
const parts = raw.split(/\s+/);
|
|
143
|
-
const quota = parseInt(parts[0], 10);
|
|
144
|
-
const period = parseInt(parts[1], 10);
|
|
145
|
-
if (!isNaN(quota) && !isNaN(period) && period > 0 && quota > 0) {
|
|
146
|
-
const cores = quota / period;
|
|
147
|
-
// Sanity check: if the value looks like the node's full CPU count
|
|
148
|
-
// and we're on a platform pod, it's likely gVisor leaking the host value.
|
|
149
|
-
if (cores < cpus().length * 0.9 || !getIsPlatform()) {
|
|
150
|
-
return cores;
|
|
151
|
-
}
|
|
152
|
-
}
|
|
153
|
-
}
|
|
154
|
-
} catch {
|
|
155
|
-
/* not available */
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
// 3. Try cgroups v1.
|
|
159
|
-
try {
|
|
160
|
-
const quota = parseInt(
|
|
161
|
-
readFileSync("/sys/fs/cgroup/cpu/cpu.cfs_quota_us", "utf-8").trim(),
|
|
162
|
-
10,
|
|
163
|
-
);
|
|
164
|
-
const period = parseInt(
|
|
165
|
-
readFileSync("/sys/fs/cgroup/cpu/cpu.cfs_period_us", "utf-8").trim(),
|
|
166
|
-
10,
|
|
167
|
-
);
|
|
168
|
-
if (!isNaN(quota) && !isNaN(period) && period > 0 && quota > 0) {
|
|
169
|
-
const cores = quota / period;
|
|
170
|
-
if (cores < cpus().length * 0.9 || !getIsPlatform()) {
|
|
171
|
-
return cores;
|
|
172
|
-
}
|
|
173
|
-
}
|
|
174
|
-
} catch {
|
|
175
|
-
/* not available */
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
// 4. Fall back to the visible CPU count; 0 when even that syscall fails.
|
|
179
|
-
try {
|
|
180
|
-
return cpus().length || availableParallelism();
|
|
181
|
-
} catch {
|
|
182
|
-
return 0;
|
|
183
|
-
}
|
|
184
|
-
}
|
|
185
|
-
|
|
186
96
|
/**
|
|
187
97
|
* Read the container's CPU usage from cgroup accounting files.
|
|
188
98
|
*
|
|
@@ -8,8 +8,13 @@
|
|
|
8
8
|
*/
|
|
9
9
|
import { describe, expect, test } from "bun:test";
|
|
10
10
|
|
|
11
|
+
import type { AdmissionPolicy } from "@vellumai/gateway-client";
|
|
12
|
+
|
|
11
13
|
import type { AdmissionPolicyInput } from "./admission-policy.js";
|
|
12
|
-
import {
|
|
14
|
+
import {
|
|
15
|
+
enforceAdmissionPolicy,
|
|
16
|
+
trustedContactPromotionClearsFloor,
|
|
17
|
+
} from "./admission-policy.js";
|
|
13
18
|
|
|
14
19
|
function makeInput(
|
|
15
20
|
overrides: Partial<AdmissionPolicyInput>,
|
|
@@ -176,3 +181,28 @@ describe("enforceAdmissionPolicy — rank vs floor", () => {
|
|
|
176
181
|
expect(result.admitted).toBe(true);
|
|
177
182
|
});
|
|
178
183
|
});
|
|
184
|
+
|
|
185
|
+
// ---------------------------------------------------------------------------
|
|
186
|
+
// Guardian approval reach: which floors a trusted-contact promotion clears
|
|
187
|
+
// ---------------------------------------------------------------------------
|
|
188
|
+
|
|
189
|
+
describe("trustedContactPromotionClearsFloor", () => {
|
|
190
|
+
const clears: AdmissionPolicy[] = [
|
|
191
|
+
"trusted_contacts",
|
|
192
|
+
"any_contact",
|
|
193
|
+
"strangers",
|
|
194
|
+
];
|
|
195
|
+
const doesNotClear: AdmissionPolicy[] = ["guardian_only", "no_one"];
|
|
196
|
+
|
|
197
|
+
for (const policy of clears) {
|
|
198
|
+
test(`${policy} is cleared by a trusted-contact promotion`, () => {
|
|
199
|
+
expect(trustedContactPromotionClearsFloor(policy)).toBe(true);
|
|
200
|
+
});
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
for (const policy of doesNotClear) {
|
|
204
|
+
test(`${policy} is not cleared by a trusted-contact promotion`, () => {
|
|
205
|
+
expect(trustedContactPromotionClearsFloor(policy)).toBe(false);
|
|
206
|
+
});
|
|
207
|
+
}
|
|
208
|
+
});
|
|
@@ -102,6 +102,24 @@ const POLICIES_THAT_COULD_UPGRADE: ReadonlySet<AdmissionPolicy> = new Set([
|
|
|
102
102
|
"strangers",
|
|
103
103
|
]);
|
|
104
104
|
|
|
105
|
+
/**
|
|
106
|
+
* Whether promoting a caller to a trusted contact would lift them past this
|
|
107
|
+
* floor.
|
|
108
|
+
*
|
|
109
|
+
* A guardian approving an access request activates the requester as a trusted
|
|
110
|
+
* contact (directly for voice, per `guardian-request-resolvers.ts`; via the
|
|
111
|
+
* minted verification code for text), so approval only helps on floors at or
|
|
112
|
+
* below the trusted-contact rank. `guardian_only` and `no_one` sit above it:
|
|
113
|
+
* an approved caller would still be below the floor, so callers denied by
|
|
114
|
+
* those floors must not be routed into an approval flow that cannot admit
|
|
115
|
+
* them.
|
|
116
|
+
*/
|
|
117
|
+
export function trustedContactPromotionClearsFloor(
|
|
118
|
+
policy: AdmissionPolicy,
|
|
119
|
+
): boolean {
|
|
120
|
+
return ADMISSION_FLOOR[policy] <= TRUST_CLASS_RANK.trusted_contact;
|
|
121
|
+
}
|
|
122
|
+
|
|
105
123
|
/**
|
|
106
124
|
* Enforce the admission policy floor against the resolved trust class.
|
|
107
125
|
*
|
|
@@ -39,7 +39,10 @@ import {
|
|
|
39
39
|
updateConnection,
|
|
40
40
|
} from "../../providers/inference/connections.js";
|
|
41
41
|
import { PROVIDER_CATALOG } from "../../providers/model-catalog.js";
|
|
42
|
-
import {
|
|
42
|
+
import {
|
|
43
|
+
isVellumManagedConnection,
|
|
44
|
+
VELLUM_MANAGED_PROVIDER,
|
|
45
|
+
} from "../../providers/vellum-model-routing.js";
|
|
43
46
|
import { credentialKey } from "../../security/credential-key.js";
|
|
44
47
|
import { deleteSecureKeyAsync } from "../../security/secure-keys.js";
|
|
45
48
|
import {
|
|
@@ -192,6 +195,39 @@ function deriveConnectionAuth(provider: string, credential: unknown): Auth {
|
|
|
192
195
|
return derived;
|
|
193
196
|
}
|
|
194
197
|
|
|
198
|
+
/**
|
|
199
|
+
* Platform auth and `provider: "vellum"` record the same fact (the managed
|
|
200
|
+
* route) in two columns, and derivation always sets them together. Only an
|
|
201
|
+
* explicit `auth` object can split them, producing a row dispatch bills to
|
|
202
|
+
* the platform while every provider-keyed check reads it as BYOK, or the
|
|
203
|
+
* reverse.
|
|
204
|
+
*/
|
|
205
|
+
function assertAuthMatchesProvider(provider: string, auth: Auth): void {
|
|
206
|
+
const managedAuth = auth.type === "platform";
|
|
207
|
+
const managedProvider = provider === VELLUM_MANAGED_PROVIDER;
|
|
208
|
+
if (managedAuth === managedProvider) {
|
|
209
|
+
return;
|
|
210
|
+
}
|
|
211
|
+
throw new BadRequestError(
|
|
212
|
+
managedAuth
|
|
213
|
+
? `Auth type "platform" is only valid for provider "${VELLUM_MANAGED_PROVIDER}", not "${provider}". Vellum-managed routing is selected by the provider, so omit "auth" to derive it.`
|
|
214
|
+
: `Provider "${VELLUM_MANAGED_PROVIDER}" is always platform-authenticated; "${auth.type}" auth is not valid for it. Omit "auth" to derive it, or name a real provider for key-based auth.`,
|
|
215
|
+
);
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
/**
|
|
219
|
+
* Stable form of an auth object for equality checks. Auth values are flat, so
|
|
220
|
+
* sorting the entries is enough to make two encodings of the same auth
|
|
221
|
+
* compare equal regardless of key order.
|
|
222
|
+
*/
|
|
223
|
+
function authFingerprint(auth: Auth): string {
|
|
224
|
+
return JSON.stringify(
|
|
225
|
+
Object.fromEntries(
|
|
226
|
+
Object.entries(auth).sort(([a], [b]) => a.localeCompare(b)),
|
|
227
|
+
),
|
|
228
|
+
);
|
|
229
|
+
}
|
|
230
|
+
|
|
195
231
|
// ---------------------------------------------------------------------------
|
|
196
232
|
// Handlers
|
|
197
233
|
// ---------------------------------------------------------------------------
|
|
@@ -300,6 +336,9 @@ async function handleCreateConnection({ body = {} }: RouteHandlerArgs) {
|
|
|
300
336
|
if (!authResult.success) {
|
|
301
337
|
throw new BadRequestError(`Invalid auth: ${authResult.error.message}`);
|
|
302
338
|
}
|
|
339
|
+
if (auth !== undefined) {
|
|
340
|
+
assertAuthMatchesProvider(providerResult.data, authResult.data);
|
|
341
|
+
}
|
|
303
342
|
|
|
304
343
|
const labelRaw = body.label;
|
|
305
344
|
if (
|
|
@@ -395,6 +434,16 @@ async function handleUpdateConnection({
|
|
|
395
434
|
if (!authResult.success) {
|
|
396
435
|
throw new BadRequestError(`Invalid auth: ${authResult.error.message}`);
|
|
397
436
|
}
|
|
437
|
+
// The pairing is enforced on an actual auth change, not on the field being
|
|
438
|
+
// present: the web editor and the CLI both resend the stored auth on every
|
|
439
|
+
// edit, so a row whose columns already disagree stays relabelable and
|
|
440
|
+
// re-pointable.
|
|
441
|
+
if (
|
|
442
|
+
body.auth !== undefined &&
|
|
443
|
+
authFingerprint(authResult.data) !== authFingerprint(existing.auth)
|
|
444
|
+
) {
|
|
445
|
+
assertAuthMatchesProvider(existing.provider, authResult.data);
|
|
446
|
+
}
|
|
398
447
|
|
|
399
448
|
const labelRaw = body.label;
|
|
400
449
|
if (
|
|
@@ -643,7 +692,7 @@ export const ROUTES: RouteDefinition[] = [
|
|
|
643
692
|
},
|
|
644
693
|
summary: "Create a provider connection",
|
|
645
694
|
description:
|
|
646
|
-
"Create a new named provider connection. When auth is omitted it is derived from the provider (keyless providers get none, vellum gets platform, everything else needs credential for api_key auth). Fails with 409 if a connection with this name already exists.",
|
|
695
|
+
"Create a new named provider connection. When auth is omitted it is derived from the provider (keyless providers get none, vellum gets platform, everything else needs credential for api_key auth). An explicit auth object must agree with the provider; platform auth belongs to vellum and only to vellum. Fails with 409 if a connection with this name already exists.",
|
|
647
696
|
tags: ["inference"],
|
|
648
697
|
requestBody: z.object({
|
|
649
698
|
name: z.string().min(1),
|
|
@@ -672,7 +721,7 @@ export const ROUTES: RouteDefinition[] = [
|
|
|
672
721
|
},
|
|
673
722
|
summary: "Update a provider connection",
|
|
674
723
|
description:
|
|
675
|
-
"Update an existing connection. Cannot rename or change the provider. Omitting auth keeps the stored auth; passing credential alone rotates the key via provider-derived api_key auth. For the Vellum-managed connection (vellum) the auth is locked to platform; label remains editable.",
|
|
724
|
+
"Update an existing connection. Cannot rename or change the provider. Omitting auth keeps the stored auth; passing credential alone rotates the key via provider-derived api_key auth. An explicit auth object must agree with the connection's provider; platform auth belongs to vellum and only to vellum. For the Vellum-managed connection (vellum) the auth is locked to platform; label remains editable.",
|
|
676
725
|
tags: ["inference"],
|
|
677
726
|
pathParams: [{ name: "name", description: "Connection name" }],
|
|
678
727
|
requestBody: z.object({
|
|
@@ -116,7 +116,10 @@ async function handleEmitSignal({ body = {} }: RouteHandlerArgs) {
|
|
|
116
116
|
const EditNotificationParams = z
|
|
117
117
|
.object({
|
|
118
118
|
id: z.string().min(1).describe("Feed item id (notif:<uuid>) or bare uuid"),
|
|
119
|
-
title: z
|
|
119
|
+
title: z
|
|
120
|
+
.string()
|
|
121
|
+
.optional()
|
|
122
|
+
.describe("New title. An empty value is ignored, never cleared."),
|
|
120
123
|
body: z.string().optional(),
|
|
121
124
|
urgency: UrgencySchema.optional(),
|
|
122
125
|
status: z.enum(["new", "seen", "acted_on", "dismissed"]).optional(),
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Platform route handlers for the shared HTTP/IPC route table.
|
|
3
3
|
*
|
|
4
|
-
* Serves
|
|
4
|
+
* Serves ten operations:
|
|
5
5
|
* - platform_status (GET platform/status): aggregates platform context,
|
|
6
6
|
* credentials, assistant ID, and webhook secret. (Velay tunnel status
|
|
7
7
|
* lives on the gateway — see gateway_status.)
|
|
@@ -18,6 +18,10 @@
|
|
|
18
18
|
* - platform_subscription (GET platform/subscription): fetches the org's
|
|
19
19
|
* current plan, subscription status, and entitlements.
|
|
20
20
|
* - platform_plans (GET platform/plans): fetches the plan catalog with pricing.
|
|
21
|
+
* - platform_invoices_list (GET platform/invoices): fetches one
|
|
22
|
+
* cursor-paginated page of the org's Stripe invoice history.
|
|
23
|
+
* - platform_invoices_by_id_get (GET platform/invoices/:id): pages through the
|
|
24
|
+
* invoice list and returns a single invoice by Stripe invoice ID.
|
|
21
25
|
*/
|
|
22
26
|
|
|
23
27
|
import { z } from "zod";
|
|
@@ -38,6 +42,7 @@ import { ACTOR_PRINCIPALS, LOCAL_PRINCIPALS } from "../auth/route-policy.js";
|
|
|
38
42
|
import {
|
|
39
43
|
BadRequestError,
|
|
40
44
|
InternalError,
|
|
45
|
+
NotFoundError,
|
|
41
46
|
UnprocessableEntityError,
|
|
42
47
|
} from "./errors.js";
|
|
43
48
|
import type { RouteDefinition, RouteHandlerArgs } from "./types.js";
|
|
@@ -126,6 +131,28 @@ const PlatformCreditsResponseSchema = z.object({
|
|
|
126
131
|
});
|
|
127
132
|
type PlatformCreditsResponse = z.infer<typeof PlatformCreditsResponseSchema>;
|
|
128
133
|
|
|
134
|
+
const PlatformInvoiceSchema = z.object({
|
|
135
|
+
id: z.string(),
|
|
136
|
+
number: z.string().nullable(),
|
|
137
|
+
status: z.string().nullable(),
|
|
138
|
+
currency: z.string(),
|
|
139
|
+
amount_due: z.number(),
|
|
140
|
+
amount_paid: z.number(),
|
|
141
|
+
amount_remaining: z.number(),
|
|
142
|
+
created: z.number(),
|
|
143
|
+
hosted_invoice_url: z.string().nullable(),
|
|
144
|
+
invoice_pdf: z.string().nullable(),
|
|
145
|
+
});
|
|
146
|
+
type PlatformInvoice = z.infer<typeof PlatformInvoiceSchema>;
|
|
147
|
+
|
|
148
|
+
const PlatformInvoicesListResponseSchema = z.object({
|
|
149
|
+
invoices: z.array(PlatformInvoiceSchema),
|
|
150
|
+
has_more: z.boolean(),
|
|
151
|
+
});
|
|
152
|
+
type PlatformInvoicesListResponse = z.infer<
|
|
153
|
+
typeof PlatformInvoicesListResponseSchema
|
|
154
|
+
>;
|
|
155
|
+
|
|
129
156
|
const SubscriptionPackageSchema = z.object({
|
|
130
157
|
key: z.string(),
|
|
131
158
|
name: z.string(),
|
|
@@ -383,15 +410,47 @@ async function handlePlatformCredits(
|
|
|
383
410
|
};
|
|
384
411
|
}
|
|
385
412
|
|
|
413
|
+
/** Default per-request timeout for platform fetches. */
|
|
414
|
+
const PLATFORM_FETCH_TIMEOUT_MS = 10_000;
|
|
415
|
+
|
|
416
|
+
interface FetchPlatformJsonOptions {
|
|
417
|
+
/**
|
|
418
|
+
* Abort signal from the caller's request. Combined with the per-request
|
|
419
|
+
* timeout so whichever fires first cancels the fetch.
|
|
420
|
+
*/
|
|
421
|
+
signal?: AbortSignal;
|
|
422
|
+
/**
|
|
423
|
+
* Per-request timeout override in milliseconds. Defaults to
|
|
424
|
+
* PLATFORM_FETCH_TIMEOUT_MS; callers with an aggregate deadline (the
|
|
425
|
+
* invoices_get cursor walk) pass the remaining budget instead.
|
|
426
|
+
*/
|
|
427
|
+
timeoutMs?: number;
|
|
428
|
+
/**
|
|
429
|
+
* When set, an upstream HTTP 400 is surfaced as a BadRequestError combining
|
|
430
|
+
* the response detail with this hint, so caller-correctable input errors
|
|
431
|
+
* (e.g. an invalid pagination cursor) keep their 400 status. When unset, a
|
|
432
|
+
* 400 falls through to the generic InternalError path.
|
|
433
|
+
*/
|
|
434
|
+
badRequestHint?: string;
|
|
435
|
+
/**
|
|
436
|
+
* When set, an upstream HTTP 404 resolves to this value instead of the
|
|
437
|
+
* generic InternalError path, for resources where the platform signals
|
|
438
|
+
* "nothing here" with a 404 (e.g. the invoice list for an organization
|
|
439
|
+
* without invoice history). When unset, a 404 stays an InternalError.
|
|
440
|
+
*/
|
|
441
|
+
notFoundValue?: unknown;
|
|
442
|
+
}
|
|
443
|
+
|
|
386
444
|
/**
|
|
387
445
|
* GET a JSON resource from the platform using the assistant's stored platform
|
|
388
446
|
* credentials. Throws UnprocessableEntityError when credentials are missing and
|
|
389
447
|
* InternalError on transport / non-2xx failures; `label` names the resource in
|
|
390
|
-
* those error messages.
|
|
448
|
+
* those error messages. See FetchPlatformJsonOptions for per-call overrides.
|
|
391
449
|
*/
|
|
392
450
|
async function fetchPlatformJson(
|
|
393
451
|
path: string,
|
|
394
452
|
label: string,
|
|
453
|
+
options?: FetchPlatformJsonOptions,
|
|
395
454
|
): Promise<unknown> {
|
|
396
455
|
const context = await resolvePlatformCallbackRegistrationContext();
|
|
397
456
|
|
|
@@ -402,6 +461,9 @@ async function fetchPlatformJson(
|
|
|
402
461
|
}
|
|
403
462
|
|
|
404
463
|
const url = `${context.platformBaseUrl}${path}`;
|
|
464
|
+
const timeout = AbortSignal.timeout(
|
|
465
|
+
options?.timeoutMs ?? PLATFORM_FETCH_TIMEOUT_MS,
|
|
466
|
+
);
|
|
405
467
|
let response: Response;
|
|
406
468
|
try {
|
|
407
469
|
response = await fetch(url, {
|
|
@@ -410,7 +472,9 @@ async function fetchPlatformJson(
|
|
|
410
472
|
Authorization: context.authHeader,
|
|
411
473
|
Accept: "application/json",
|
|
412
474
|
},
|
|
413
|
-
signal:
|
|
475
|
+
signal: options?.signal
|
|
476
|
+
? AbortSignal.any([options.signal, timeout])
|
|
477
|
+
: timeout,
|
|
414
478
|
});
|
|
415
479
|
} catch (err) {
|
|
416
480
|
throw new InternalError(
|
|
@@ -419,7 +483,16 @@ async function fetchPlatformJson(
|
|
|
419
483
|
}
|
|
420
484
|
|
|
421
485
|
if (!response.ok) {
|
|
486
|
+
if (response.status === 404 && options?.notFoundValue !== undefined) {
|
|
487
|
+
return options.notFoundValue;
|
|
488
|
+
}
|
|
422
489
|
const detail = await response.text().catch(() => "");
|
|
490
|
+
if (response.status === 400 && options?.badRequestHint) {
|
|
491
|
+
throw new BadRequestError(
|
|
492
|
+
`Platform rejected the ${label} request${detail ? `: ${detail}` : ""}. ` +
|
|
493
|
+
options.badRequestHint,
|
|
494
|
+
);
|
|
495
|
+
}
|
|
423
496
|
throw new InternalError(
|
|
424
497
|
`Failed to fetch ${label} (HTTP ${response.status}): ${detail}`,
|
|
425
498
|
);
|
|
@@ -485,6 +558,129 @@ async function handlePlatformPlans(
|
|
|
485
558
|
return { plans: data.plans ?? [] };
|
|
486
559
|
}
|
|
487
560
|
|
|
561
|
+
/**
|
|
562
|
+
* Fetch one cursor-paginated page of the org's Stripe invoice history from
|
|
563
|
+
* the platform. Pass the previous page's last invoice id as `startingAfter`
|
|
564
|
+
* to fetch the next (older) page.
|
|
565
|
+
*/
|
|
566
|
+
async function fetchPlatformInvoicesPage(
|
|
567
|
+
startingAfter: string | undefined,
|
|
568
|
+
options?: FetchPlatformJsonOptions,
|
|
569
|
+
): Promise<PlatformInvoicesListResponse> {
|
|
570
|
+
let path = "/v1/organizations/billing/invoices/";
|
|
571
|
+
if (startingAfter) {
|
|
572
|
+
path += `?${new URLSearchParams({ starting_after: startingAfter })}`;
|
|
573
|
+
}
|
|
574
|
+
return (await fetchPlatformJson(path, "invoices", {
|
|
575
|
+
...options,
|
|
576
|
+
// The platform returns 404 for an organization without invoice history;
|
|
577
|
+
// the web client treats that as an empty page
|
|
578
|
+
// (clients/web/src/domains/settings/components/invoices-table.tsx) and
|
|
579
|
+
// the daemon matches it.
|
|
580
|
+
notFoundValue: { invoices: [], has_more: false },
|
|
581
|
+
})) as PlatformInvoicesListResponse;
|
|
582
|
+
}
|
|
583
|
+
|
|
584
|
+
async function handlePlatformInvoicesList(
|
|
585
|
+
args: RouteHandlerArgs,
|
|
586
|
+
): Promise<PlatformInvoicesListResponse> {
|
|
587
|
+
const startingAfter = args.queryParams?.starting_after;
|
|
588
|
+
return await fetchPlatformInvoicesPage(startingAfter, {
|
|
589
|
+
signal: args.abortSignal,
|
|
590
|
+
// The platform returns 400 for an invalid or expired starting_after
|
|
591
|
+
// cursor. Only a caller-supplied cursor is caller-correctable, so the
|
|
592
|
+
// hint (and the BadRequestError it triggers) applies just when one was
|
|
593
|
+
// passed; a 400 on a cursor-less request stays an InternalError.
|
|
594
|
+
badRequestHint: startingAfter
|
|
595
|
+
? "If you passed starting_after, use the id of the last invoice from the previous page."
|
|
596
|
+
: undefined,
|
|
597
|
+
});
|
|
598
|
+
}
|
|
599
|
+
|
|
600
|
+
/**
|
|
601
|
+
* Runaway guard for the invoices_get cursor walk: 25 pages is 2,500
|
|
602
|
+
* invoices at the platform's 100-per-page size.
|
|
603
|
+
*/
|
|
604
|
+
export const MAX_INVOICE_PAGES = 25;
|
|
605
|
+
|
|
606
|
+
/**
|
|
607
|
+
* Aggregate wall-clock deadline for the invoices_get cursor walk, matching
|
|
608
|
+
* the gateway IPC client's 30s request timeout. Guards against the walk
|
|
609
|
+
* running long after the caller has given up, since a gateway IPC timeout
|
|
610
|
+
* does not abort the request signal.
|
|
611
|
+
*/
|
|
612
|
+
export const INVOICE_WALK_DEADLINE_MS = 30_000;
|
|
613
|
+
|
|
614
|
+
function invoiceWalkTimeoutError(id: string): InternalError {
|
|
615
|
+
return new InternalError(
|
|
616
|
+
`Invoice lookup for "${id}" timed out after ` +
|
|
617
|
+
`${INVOICE_WALK_DEADLINE_MS / 1000} seconds while paging invoice history`,
|
|
618
|
+
);
|
|
619
|
+
}
|
|
620
|
+
|
|
621
|
+
async function handlePlatformInvoiceGet(
|
|
622
|
+
args: RouteHandlerArgs,
|
|
623
|
+
): Promise<PlatformInvoice> {
|
|
624
|
+
const id = args.pathParams?.id;
|
|
625
|
+
if (!id) {
|
|
626
|
+
throw new BadRequestError("invoice id is required");
|
|
627
|
+
}
|
|
628
|
+
|
|
629
|
+
// There is no per-invoice platform endpoint, so walk the paginated list
|
|
630
|
+
// until the invoice turns up or the cursor is exhausted.
|
|
631
|
+
const walkStartedAt = Date.now();
|
|
632
|
+
let cursor: string | undefined;
|
|
633
|
+
for (let pages = 0; pages < MAX_INVOICE_PAGES; pages++) {
|
|
634
|
+
// Stop walking pages once the caller has gone away (gateway IPC timeout,
|
|
635
|
+
// disconnected HTTP client). In-flight fetches are cancelled via the
|
|
636
|
+
// signal passed to fetchPlatformInvoicesPage.
|
|
637
|
+
if (args.abortSignal?.aborted) {
|
|
638
|
+
throw new InternalError(
|
|
639
|
+
`Invoice lookup for "${id}" aborted: caller disconnected`,
|
|
640
|
+
);
|
|
641
|
+
}
|
|
642
|
+
const remainingMs = walkStartedAt + INVOICE_WALK_DEADLINE_MS - Date.now();
|
|
643
|
+
if (remainingMs <= 0) {
|
|
644
|
+
throw invoiceWalkTimeoutError(id);
|
|
645
|
+
}
|
|
646
|
+
// Bound the page fetch by the remaining aggregate budget so a fetch
|
|
647
|
+
// started just under the deadline cannot run the walk past it.
|
|
648
|
+
let page: PlatformInvoicesListResponse;
|
|
649
|
+
try {
|
|
650
|
+
// No badRequestHint: the cursor is internally generated, so an
|
|
651
|
+
// upstream 400 here is an internal failure, not a caller input error.
|
|
652
|
+
page = await fetchPlatformInvoicesPage(cursor, {
|
|
653
|
+
signal: args.abortSignal,
|
|
654
|
+
timeoutMs: Math.min(PLATFORM_FETCH_TIMEOUT_MS, remainingMs),
|
|
655
|
+
});
|
|
656
|
+
} catch (err) {
|
|
657
|
+
// A fetch cancelled by the deadline remainder should read as the
|
|
658
|
+
// walk's aggregate timeout, not a generic network failure.
|
|
659
|
+
if (
|
|
660
|
+
!args.abortSignal?.aborted &&
|
|
661
|
+
Date.now() - walkStartedAt >= INVOICE_WALK_DEADLINE_MS
|
|
662
|
+
) {
|
|
663
|
+
throw invoiceWalkTimeoutError(id);
|
|
664
|
+
}
|
|
665
|
+
throw err;
|
|
666
|
+
}
|
|
667
|
+
const match = page.invoices.find((invoice) => invoice.id === id);
|
|
668
|
+
if (match) {
|
|
669
|
+
return match;
|
|
670
|
+
}
|
|
671
|
+
if (!page.has_more || page.invoices.length === 0) {
|
|
672
|
+
throw new NotFoundError(
|
|
673
|
+
`Invoice "${id}" not found. Run 'assistant platform invoices list' to see available invoices.`,
|
|
674
|
+
);
|
|
675
|
+
}
|
|
676
|
+
cursor = page.invoices.at(-1)!.id;
|
|
677
|
+
}
|
|
678
|
+
|
|
679
|
+
throw new InternalError(
|
|
680
|
+
`Invoice "${id}" not found in the first ${MAX_INVOICE_PAGES} pages of invoice history`,
|
|
681
|
+
);
|
|
682
|
+
}
|
|
683
|
+
|
|
488
684
|
// ---------------------------------------------------------------------------
|
|
489
685
|
// Route definitions
|
|
490
686
|
// ---------------------------------------------------------------------------
|
|
@@ -611,4 +807,43 @@ export const ROUTES: RouteDefinition[] = [
|
|
|
611
807
|
handler: handlePlatformPlans,
|
|
612
808
|
responseBody: PlatformPlansResponseSchema,
|
|
613
809
|
},
|
|
810
|
+
{
|
|
811
|
+
operationId: "platform_invoices_list",
|
|
812
|
+
endpoint: "platform/invoices",
|
|
813
|
+
method: "GET",
|
|
814
|
+
policy: {
|
|
815
|
+
requiredScopes: ["settings.read"],
|
|
816
|
+
allowedPrincipalTypes: ACTOR_PRINCIPALS,
|
|
817
|
+
},
|
|
818
|
+
summary: "List one page of the organization's Stripe invoices",
|
|
819
|
+
description:
|
|
820
|
+
"Fetches one page of the org's Stripe invoice history (newest first) from the platform billing invoices endpoint. Amounts are in the currency's minor units. When has_more is true, pass the last invoice's id as starting_after to fetch the next page.",
|
|
821
|
+
tags: ["platform"],
|
|
822
|
+
queryParams: [
|
|
823
|
+
{
|
|
824
|
+
name: "starting_after",
|
|
825
|
+
type: "string",
|
|
826
|
+
required: false,
|
|
827
|
+
description:
|
|
828
|
+
"Cursor: return invoices older than the invoice with this id (from the previous page's last entry).",
|
|
829
|
+
},
|
|
830
|
+
],
|
|
831
|
+
handler: handlePlatformInvoicesList,
|
|
832
|
+
responseBody: PlatformInvoicesListResponseSchema,
|
|
833
|
+
},
|
|
834
|
+
{
|
|
835
|
+
operationId: "platform_invoices_by_id_get",
|
|
836
|
+
endpoint: "platform/invoices/:id",
|
|
837
|
+
method: "GET",
|
|
838
|
+
policy: {
|
|
839
|
+
requiredScopes: ["settings.read"],
|
|
840
|
+
allowedPrincipalTypes: ACTOR_PRINCIPALS,
|
|
841
|
+
},
|
|
842
|
+
summary: "Get a single Stripe invoice by ID",
|
|
843
|
+
description:
|
|
844
|
+
"Pages through the org's invoice list from the platform and returns the invoice matching the given Stripe invoice ID (e.g. in_xxx). 404 if no such invoice.",
|
|
845
|
+
tags: ["platform"],
|
|
846
|
+
handler: handlePlatformInvoiceGet,
|
|
847
|
+
responseBody: PlatformInvoiceSchema,
|
|
848
|
+
},
|
|
614
849
|
];
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import { isAssistantFeatureFlagEnabled } from "../../../config/assistant-feature-flags.js";
|
|
2
|
-
import { getConfig } from "../../../config/loader.js";
|
|
3
2
|
import { RouteError } from "../errors.js";
|
|
4
3
|
|
|
5
4
|
/**
|
|
@@ -16,7 +15,7 @@ import { RouteError } from "../errors.js";
|
|
|
16
15
|
const PLAYGROUND_DISABLED_CODE = "playground_disabled";
|
|
17
16
|
|
|
18
17
|
function isPlaygroundEnabled(): boolean {
|
|
19
|
-
return isAssistantFeatureFlagEnabled("compaction-playground"
|
|
18
|
+
return isAssistantFeatureFlagEnabled("compaction-playground");
|
|
20
19
|
}
|
|
21
20
|
|
|
22
21
|
/**
|
|
@@ -4,10 +4,8 @@ import {
|
|
|
4
4
|
conversationMetadataSyncTag,
|
|
5
5
|
SYNC_TAGS,
|
|
6
6
|
} from "../../daemon/message-types/sync.js";
|
|
7
|
-
import {
|
|
8
|
-
|
|
9
|
-
SlackApiError,
|
|
10
|
-
} from "../../messaging/providers/slack/api.js";
|
|
7
|
+
import { getSlackConversationInfo } from "../../messaging/providers/slack/api.js";
|
|
8
|
+
import { SlackApiError } from "../../messaging/providers/slack/web-api-transport.js";
|
|
11
9
|
import {
|
|
12
10
|
getBindingByConversation,
|
|
13
11
|
updateExternalChatName,
|