@vellumai/assistant 0.10.6-staging.1 → 0.10.6
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/openapi.yaml +43 -6
- package/package.json +1 -1
- package/src/__tests__/attachment-stored-path-annotation.test.ts +151 -0
- package/src/__tests__/attachments-store.test.ts +37 -1
- package/src/__tests__/attachments.test.ts +53 -0
- package/src/__tests__/channel-reply-delivery.test.ts +214 -5
- package/src/__tests__/config-loader-backfill.test.ts +16 -36
- package/src/__tests__/image-source-path-reinject.test.ts +155 -14
- package/src/__tests__/managed-profile-guard.test.ts +2 -0
- package/src/__tests__/mtime-cache.test.ts +265 -80
- package/src/__tests__/plugin-config-data-migration.test.ts +2 -4
- package/src/__tests__/plugin-effective-enabled-set.test.ts +9 -6
- package/src/__tests__/workspace-migration-125-repoint-managed-connections-to-vellum.test.ts +140 -0
- package/src/agent/attachments.ts +55 -13
- package/src/cli/commands/platform/__tests__/credits.test.ts +111 -0
- package/src/cli/commands/platform/index.ts +86 -4
- package/src/cli/lib/__tests__/confirm-prompt.test.ts +9 -9
- package/src/cli/lib/diff-plugin.ts +7 -3
- package/src/cli/lib/inspect-plugin.ts +28 -10
- package/src/cli/lib/install-from-github.ts +69 -24
- package/src/cli/lib/ipc-params.ts +3 -3
- package/src/cli/lib/list-installed-plugins.ts +11 -5
- package/src/cli/lib/merge-plugin-tree.ts +15 -9
- package/src/cli/lib/plugin-fingerprint.ts +0 -6
- package/src/cli/lib/publish-plugin.ts +1 -1
- package/src/cli/lib/unknown-command.ts +3 -1
- package/src/cli/lib/upgrade-plugin.ts +2 -5
- package/src/config/__tests__/sync-gated-profiles.test.ts +2 -2
- package/src/config/seed-inference-profiles.ts +5 -4
- package/src/daemon/conversation-lifecycle.ts +37 -15
- package/src/daemon/conversation-messaging.ts +56 -28
- package/src/daemon/conversation.ts +2 -2
- package/src/heartbeat/__tests__/heartbeat-service.test.ts +6 -0
- package/src/hooks/__tests__/hook-live-reload.test.ts +110 -96
- package/src/hooks/hook-loader.ts +57 -144
- package/src/hooks/registry.ts +13 -9
- package/src/persistence/attachments-store.ts +2 -1
- package/src/persistence/conversation-crud.ts +24 -0
- package/src/plugin-api/types.ts +4 -1
- package/src/plugins/mtime-cache.ts +347 -33
- package/src/plugins/pipeline.ts +3 -2
- package/src/providers/__tests__/inference.test.ts +42 -31
- package/src/providers/inference/__tests__/connections-label.test.ts +9 -28
- package/src/providers/inference/auth.ts +1 -2
- package/src/providers/inference/backfill.ts +7 -5
- package/src/providers/inference/connections.ts +54 -30
- package/src/runtime/channel-reply-delivery.ts +83 -24
- package/src/runtime/channel-retry-sweep.ts +4 -0
- package/src/runtime/{slack-no-response.ts → no-response.ts} +26 -6
- package/src/runtime/routes/__tests__/conversation-query-routes.test.ts +43 -3
- package/src/runtime/routes/__tests__/inference-provider-connection-routes.test.ts +61 -24
- package/src/runtime/routes/conversation-query-routes.ts +18 -1
- package/src/runtime/routes/inbound-stages/background-dispatch.ts +1 -1
- package/src/runtime/routes/inference-provider-connection-routes.ts +4 -3
- package/src/runtime/routes/platform-routes.ts +82 -1
- package/src/runtime/slack-reply-session.ts +1 -1
- package/src/workspace/migrations/125-repoint-managed-connections-to-vellum.ts +99 -0
- package/src/workspace/migrations/registry.ts +2 -0
|
@@ -560,11 +560,7 @@ describe("PATCH with label", () => {
|
|
|
560
560
|
// ── Managed-connection write protection ──────────────────────────────────────
|
|
561
561
|
|
|
562
562
|
describe("Managed connection write protection", () => {
|
|
563
|
-
const MANAGED_NAMES = [
|
|
564
|
-
"anthropic-managed",
|
|
565
|
-
"openai-managed",
|
|
566
|
-
"gemini-managed",
|
|
567
|
-
] as const;
|
|
563
|
+
const MANAGED_NAMES = ["vellum"] as const;
|
|
568
564
|
|
|
569
565
|
describe("DELETE", () => {
|
|
570
566
|
for (const name of MANAGED_NAMES) {
|
|
@@ -590,21 +586,21 @@ describe("Managed connection write protection", () => {
|
|
|
590
586
|
// Even though a profile references the managed connection, the error
|
|
591
587
|
// should be the managed-protection 400, not the references-409.
|
|
592
588
|
seedConnection({
|
|
593
|
-
name: "
|
|
589
|
+
name: "vellum",
|
|
594
590
|
provider: "anthropic",
|
|
595
591
|
auth: { type: "platform" },
|
|
596
592
|
});
|
|
597
593
|
fakeConfig = {
|
|
598
594
|
llm: {
|
|
599
595
|
profiles: {
|
|
600
|
-
balanced: { provider_connection: "
|
|
596
|
+
balanced: { provider_connection: "vellum" },
|
|
601
597
|
},
|
|
602
598
|
},
|
|
603
599
|
};
|
|
604
600
|
|
|
605
601
|
const err = await call(
|
|
606
602
|
findHandler("inference_provider_connections_delete"),
|
|
607
|
-
{ pathParams: { name: "
|
|
603
|
+
{ pathParams: { name: "vellum" } },
|
|
608
604
|
).catch((e: unknown) => e);
|
|
609
605
|
|
|
610
606
|
expect(err).toBeInstanceOf(BadRequestError);
|
|
@@ -656,7 +652,7 @@ describe("Managed connection write protection", () => {
|
|
|
656
652
|
|
|
657
653
|
test("allows PATCH with auth still set to platform (no-op auth change)", async () => {
|
|
658
654
|
seedConnection({
|
|
659
|
-
name: "
|
|
655
|
+
name: "vellum",
|
|
660
656
|
provider: "anthropic",
|
|
661
657
|
auth: { type: "platform" },
|
|
662
658
|
});
|
|
@@ -664,7 +660,7 @@ describe("Managed connection write protection", () => {
|
|
|
664
660
|
const result = (await call(
|
|
665
661
|
findHandler("inference_provider_connections_update"),
|
|
666
662
|
{
|
|
667
|
-
pathParams: { name: "
|
|
663
|
+
pathParams: { name: "vellum" },
|
|
668
664
|
body: {
|
|
669
665
|
auth: { type: "platform" },
|
|
670
666
|
label: "Vellum-managed Anthropic",
|
|
@@ -678,7 +674,7 @@ describe("Managed connection write protection", () => {
|
|
|
678
674
|
describe("PATCH label (allowed)", () => {
|
|
679
675
|
test("allows relabeling a managed connection", async () => {
|
|
680
676
|
seedConnection({
|
|
681
|
-
name: "
|
|
677
|
+
name: "vellum",
|
|
682
678
|
provider: "openai",
|
|
683
679
|
auth: { type: "platform" },
|
|
684
680
|
});
|
|
@@ -686,7 +682,7 @@ describe("Managed connection write protection", () => {
|
|
|
686
682
|
const result = (await call(
|
|
687
683
|
findHandler("inference_provider_connections_update"),
|
|
688
684
|
{
|
|
689
|
-
pathParams: { name: "
|
|
685
|
+
pathParams: { name: "vellum" },
|
|
690
686
|
body: { auth: { type: "platform" }, label: "Custom Label" },
|
|
691
687
|
},
|
|
692
688
|
)) as { label: string | null };
|
|
@@ -698,11 +694,7 @@ describe("Managed connection write protection", () => {
|
|
|
698
694
|
// ── isManaged response flag ───────────────────────────────────────────────────
|
|
699
695
|
|
|
700
696
|
describe("isManaged flag on connection responses", () => {
|
|
701
|
-
const MANAGED_NAMES = [
|
|
702
|
-
"anthropic-managed",
|
|
703
|
-
"openai-managed",
|
|
704
|
-
"gemini-managed",
|
|
705
|
-
] as const;
|
|
697
|
+
const MANAGED_NAMES = ["vellum"] as const;
|
|
706
698
|
|
|
707
699
|
describe("GET list", () => {
|
|
708
700
|
test("returns isManaged: true for canonical names and false for user-created rows", async () => {
|
|
@@ -727,24 +719,69 @@ describe("isManaged flag on connection responses", () => {
|
|
|
727
719
|
const byName = Object.fromEntries(
|
|
728
720
|
result.connections.map((c) => [c.name, c.isManaged]),
|
|
729
721
|
);
|
|
730
|
-
expect(byName["
|
|
731
|
-
expect(byName["openai-managed"]).toBe(true);
|
|
732
|
-
expect(byName["gemini-managed"]).toBe(true);
|
|
722
|
+
expect(byName["vellum"]).toBe(true);
|
|
733
723
|
expect(byName["my-custom-anthropic"]).toBe(false);
|
|
734
724
|
});
|
|
725
|
+
|
|
726
|
+
test("hides orphaned legacy *-managed rows from the list", async () => {
|
|
727
|
+
// Existing installs (and fresh installs via migration 243) may still
|
|
728
|
+
// carry the pre-consolidation rows until a follow-up migration deletes
|
|
729
|
+
// them; they must not surface in the UI alongside `vellum`.
|
|
730
|
+
for (const name of [
|
|
731
|
+
"anthropic-managed",
|
|
732
|
+
"openai-managed",
|
|
733
|
+
"gemini-managed",
|
|
734
|
+
"fireworks-managed",
|
|
735
|
+
"together-managed",
|
|
736
|
+
]) {
|
|
737
|
+
seedConnection({
|
|
738
|
+
name,
|
|
739
|
+
provider: name.replace("-managed", ""),
|
|
740
|
+
auth: { type: "platform" },
|
|
741
|
+
});
|
|
742
|
+
}
|
|
743
|
+
seedConnection({
|
|
744
|
+
name: "vellum",
|
|
745
|
+
provider: "vellum",
|
|
746
|
+
auth: { type: "platform" },
|
|
747
|
+
});
|
|
748
|
+
seedConnection({
|
|
749
|
+
name: "my-openai",
|
|
750
|
+
provider: "openai",
|
|
751
|
+
auth: { type: "api_key", credential: "ref/k" },
|
|
752
|
+
});
|
|
753
|
+
|
|
754
|
+
const result = (await call(
|
|
755
|
+
findHandler("inference_provider_connections_list"),
|
|
756
|
+
{},
|
|
757
|
+
)) as { connections: Array<{ name: string }> };
|
|
758
|
+
const names = result.connections.map((c) => c.name);
|
|
759
|
+
|
|
760
|
+
expect(names).toContain("vellum");
|
|
761
|
+
expect(names).toContain("my-openai");
|
|
762
|
+
for (const legacy of [
|
|
763
|
+
"anthropic-managed",
|
|
764
|
+
"openai-managed",
|
|
765
|
+
"gemini-managed",
|
|
766
|
+
"fireworks-managed",
|
|
767
|
+
"together-managed",
|
|
768
|
+
]) {
|
|
769
|
+
expect(names).not.toContain(legacy);
|
|
770
|
+
}
|
|
771
|
+
});
|
|
735
772
|
});
|
|
736
773
|
|
|
737
774
|
describe("GET single", () => {
|
|
738
775
|
test("returns isManaged: true for a managed name", async () => {
|
|
739
776
|
seedConnection({
|
|
740
|
-
name: "
|
|
777
|
+
name: "vellum",
|
|
741
778
|
provider: "anthropic",
|
|
742
779
|
auth: { type: "platform" },
|
|
743
780
|
});
|
|
744
781
|
|
|
745
782
|
const result = (await call(
|
|
746
783
|
findHandler("inference_provider_connections_get"),
|
|
747
|
-
{ pathParams: { name: "
|
|
784
|
+
{ pathParams: { name: "vellum" } },
|
|
748
785
|
)) as { name: string; isManaged: boolean };
|
|
749
786
|
|
|
750
787
|
expect(result.isManaged).toBe(true);
|
|
@@ -786,7 +823,7 @@ describe("isManaged flag on connection responses", () => {
|
|
|
786
823
|
describe("PATCH update", () => {
|
|
787
824
|
test("returns isManaged: true after relabeling a managed connection", async () => {
|
|
788
825
|
seedConnection({
|
|
789
|
-
name: "
|
|
826
|
+
name: "vellum",
|
|
790
827
|
provider: "anthropic",
|
|
791
828
|
auth: { type: "platform" },
|
|
792
829
|
});
|
|
@@ -794,7 +831,7 @@ describe("isManaged flag on connection responses", () => {
|
|
|
794
831
|
const result = (await call(
|
|
795
832
|
findHandler("inference_provider_connections_update"),
|
|
796
833
|
{
|
|
797
|
-
pathParams: { name: "
|
|
834
|
+
pathParams: { name: "vellum" },
|
|
798
835
|
body: { auth: { type: "platform" }, label: "Vellum Anthropic" },
|
|
799
836
|
},
|
|
800
837
|
)) as { name: string; isManaged: boolean };
|
|
@@ -84,11 +84,15 @@ import { MEMORY_V2_CONSOLIDATION_SOURCE } from "../../plugins/defaults/memory/v2
|
|
|
84
84
|
import { getMemoryV3SelectionForInspectorByMessageIds } from "../../plugins/defaults/memory/v3/selection-log-store.js";
|
|
85
85
|
import {
|
|
86
86
|
createConnection,
|
|
87
|
+
getConnection,
|
|
88
|
+
LEGACY_MANAGED_CONNECTION_NAMES,
|
|
87
89
|
listConnections,
|
|
88
90
|
PROVIDERS_REQUIRING_BASE_URL_AND_MODELS,
|
|
91
|
+
VELLUM_MANAGED_CONNECTION_NAME,
|
|
89
92
|
} from "../../providers/inference/connections.js";
|
|
90
93
|
import { PROVIDER_CATALOG } from "../../providers/model-catalog.js";
|
|
91
94
|
import { initializeProviders } from "../../providers/registry.js";
|
|
95
|
+
import { MANAGED_ROUTABLE_PROVIDERS } from "../../providers/vellum-model-routing.js";
|
|
92
96
|
import { credentialKey } from "../../security/credential-key.js";
|
|
93
97
|
import { validateAllowlistFile } from "../../security/secret-allowlist.js";
|
|
94
98
|
import {
|
|
@@ -1296,9 +1300,22 @@ async function handleReplaceInferenceProfile({
|
|
|
1296
1300
|
if (!isManaged && fragment.provider && !fragment.provider_connection) {
|
|
1297
1301
|
const provider = fragment.provider as string;
|
|
1298
1302
|
const db = getDb();
|
|
1299
|
-
|
|
1303
|
+
// Exclude the orphaned legacy `*-managed` rows: they may still linger in
|
|
1304
|
+
// provider_connections on upgraded workspaces (hidden from the list route
|
|
1305
|
+
// until a follow-up migration deletes them). Auto-binding to one would keep
|
|
1306
|
+
// the profile stale and break it once those rows are removed.
|
|
1307
|
+
const [active] = listConnections(db, { provider }).filter(
|
|
1308
|
+
(c) => !LEGACY_MANAGED_CONNECTION_NAMES.has(c.name),
|
|
1309
|
+
);
|
|
1300
1310
|
if (active) {
|
|
1301
1311
|
fragment.provider_connection = active.name;
|
|
1312
|
+
} else if (
|
|
1313
|
+
MANAGED_ROUTABLE_PROVIDERS.has(provider) &&
|
|
1314
|
+
getConnection(db, VELLUM_MANAGED_CONNECTION_NAME)
|
|
1315
|
+
) {
|
|
1316
|
+
// Managed-routable providers are served by the single Vellum-managed
|
|
1317
|
+
// connection; prefer it over lazily creating a personal connection.
|
|
1318
|
+
fragment.provider_connection = VELLUM_MANAGED_CONNECTION_NAME;
|
|
1302
1319
|
} else if (!PROVIDERS_REQUIRING_BASE_URL_AND_MODELS.has(provider)) {
|
|
1303
1320
|
const connectionName = `${provider}-personal`;
|
|
1304
1321
|
const isKeyless = provider === "ollama";
|
|
@@ -46,7 +46,7 @@ import type {
|
|
|
46
46
|
MessageProcessor,
|
|
47
47
|
SlackInboundMessageMetadata,
|
|
48
48
|
} from "../../http-types.js";
|
|
49
|
-
import { hasDeliverableAssistantText } from "../../
|
|
49
|
+
import { hasDeliverableAssistantText } from "../../no-response.js";
|
|
50
50
|
import { createSlackReplySession } from "../../slack-reply-session.js";
|
|
51
51
|
import type { TaskProgressData } from "../../slack-task-progress.js";
|
|
52
52
|
import {
|
|
@@ -25,6 +25,7 @@ import {
|
|
|
25
25
|
createConnection,
|
|
26
26
|
deleteConnection,
|
|
27
27
|
getConnection,
|
|
28
|
+
LEGACY_MANAGED_CONNECTION_NAMES,
|
|
28
29
|
listConnections,
|
|
29
30
|
MANAGED_CONNECTION_NAMES,
|
|
30
31
|
PROVIDERS_REQUIRING_BASE_URL_AND_MODELS,
|
|
@@ -162,7 +163,7 @@ function handleListConnections({ queryParams = {} }: RouteHandlerArgs) {
|
|
|
162
163
|
const connections = listConnections(
|
|
163
164
|
getDb(),
|
|
164
165
|
provider ? { provider } : undefined,
|
|
165
|
-
);
|
|
166
|
+
).filter((c) => !LEGACY_MANAGED_CONNECTION_NAMES.has(c.name));
|
|
166
167
|
return { connections };
|
|
167
168
|
}
|
|
168
169
|
|
|
@@ -461,7 +462,7 @@ export const ROUTES: RouteDefinition[] = [
|
|
|
461
462
|
},
|
|
462
463
|
summary: "Update a provider connection",
|
|
463
464
|
description:
|
|
464
|
-
"Update an existing connection. Cannot rename or change the provider. For
|
|
465
|
+
"Update an existing connection. Cannot rename or change the provider. For the Vellum-managed connection (vellum) the auth is locked to platform; label remains editable.",
|
|
465
466
|
tags: ["inference"],
|
|
466
467
|
pathParams: [{ name: "name", description: "Connection name" }],
|
|
467
468
|
requestBody: z.object({
|
|
@@ -490,7 +491,7 @@ export const ROUTES: RouteDefinition[] = [
|
|
|
490
491
|
},
|
|
491
492
|
summary: "Delete a provider connection",
|
|
492
493
|
description:
|
|
493
|
-
"Delete a provider connection. Fails with 400 for
|
|
494
|
+
"Delete a provider connection. Fails with 400 for the Vellum-managed connection (vellum) which is re-seeded on boot. Fails with 409 if any profile or call-site references the connection.",
|
|
494
495
|
tags: ["inference"],
|
|
495
496
|
pathParams: [{ name: "name", description: "Connection name" }],
|
|
496
497
|
responseBody: z.object({ ok: z.literal(true) }),
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Platform route handlers for the shared HTTP/IPC route table.
|
|
3
3
|
*
|
|
4
|
-
* Serves
|
|
4
|
+
* Serves six operations:
|
|
5
5
|
* - platform_status (GET platform/status): aggregates platform context,
|
|
6
6
|
* credentials, assistant ID, webhook secret, and Velay tunnel status.
|
|
7
7
|
* - platform_connect (POST platform/connect): checks existing credentials
|
|
@@ -12,6 +12,8 @@
|
|
|
12
12
|
* registers a callback route with the platform gateway.
|
|
13
13
|
* - platform_callback_routes_list (GET platform/callback-routes): lists
|
|
14
14
|
* registered callback routes for this assistant.
|
|
15
|
+
* - platform_credits (GET platform/credits): fetches the org's remaining
|
|
16
|
+
* credit balance from the platform billing summary.
|
|
15
17
|
*/
|
|
16
18
|
|
|
17
19
|
import { z } from "zod";
|
|
@@ -118,6 +120,16 @@ type CallbackRoutesListResponse = z.infer<
|
|
|
118
120
|
typeof CallbackRoutesListResponseSchema
|
|
119
121
|
>;
|
|
120
122
|
|
|
123
|
+
const PlatformCreditsResponseSchema = z.object({
|
|
124
|
+
remaining: z.number(),
|
|
125
|
+
settled: z.number(),
|
|
126
|
+
pending: z.number(),
|
|
127
|
+
unit: z.literal("USD"),
|
|
128
|
+
stale: z.boolean(),
|
|
129
|
+
as_of: z.string(),
|
|
130
|
+
});
|
|
131
|
+
type PlatformCreditsResponse = z.infer<typeof PlatformCreditsResponseSchema>;
|
|
132
|
+
|
|
121
133
|
// ---------------------------------------------------------------------------
|
|
122
134
|
// Handlers
|
|
123
135
|
// ---------------------------------------------------------------------------
|
|
@@ -352,6 +364,60 @@ async function handleCallbackRoutesList(
|
|
|
352
364
|
return { routes };
|
|
353
365
|
}
|
|
354
366
|
|
|
367
|
+
async function handlePlatformCredits(
|
|
368
|
+
_args: RouteHandlerArgs,
|
|
369
|
+
): Promise<PlatformCreditsResponse> {
|
|
370
|
+
const context = await resolvePlatformCallbackRegistrationContext();
|
|
371
|
+
|
|
372
|
+
if (!context.platformBaseUrl || !context.authHeader) {
|
|
373
|
+
throw new UnprocessableEntityError(
|
|
374
|
+
"Platform credentials not available — run 'assistant platform connect' or set VELLUM_PLATFORM_URL",
|
|
375
|
+
);
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
const url = `${context.platformBaseUrl}/v1/organizations/billing/summary/`;
|
|
379
|
+
let response: Response;
|
|
380
|
+
try {
|
|
381
|
+
response = await fetch(url, {
|
|
382
|
+
method: "GET",
|
|
383
|
+
headers: {
|
|
384
|
+
Authorization: context.authHeader,
|
|
385
|
+
Accept: "application/json",
|
|
386
|
+
},
|
|
387
|
+
signal: AbortSignal.timeout(10_000),
|
|
388
|
+
});
|
|
389
|
+
} catch (err) {
|
|
390
|
+
throw new InternalError(
|
|
391
|
+
`Failed to fetch credit balance: ${(err as Error).message}`,
|
|
392
|
+
);
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
if (!response.ok) {
|
|
396
|
+
const detail = await response.text().catch(() => "");
|
|
397
|
+
throw new InternalError(
|
|
398
|
+
`Failed to fetch credit balance (HTTP ${response.status}): ${detail}`,
|
|
399
|
+
);
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
const summary = (await response.json()) as {
|
|
403
|
+
settled_balance_usd: string;
|
|
404
|
+
pending_compute_usd: string;
|
|
405
|
+
effective_balance_usd: string;
|
|
406
|
+
is_degraded: boolean;
|
|
407
|
+
};
|
|
408
|
+
|
|
409
|
+
return {
|
|
410
|
+
remaining: Number(summary.effective_balance_usd),
|
|
411
|
+
settled: Number(summary.settled_balance_usd),
|
|
412
|
+
pending: Number(summary.pending_compute_usd),
|
|
413
|
+
unit: "USD",
|
|
414
|
+
stale: summary.is_degraded,
|
|
415
|
+
// as_of is response receipt time; add a server as_of field if the billing
|
|
416
|
+
// summary endpoint ever returns one.
|
|
417
|
+
as_of: new Date().toISOString(),
|
|
418
|
+
};
|
|
419
|
+
}
|
|
420
|
+
|
|
355
421
|
// ---------------------------------------------------------------------------
|
|
356
422
|
// Route definitions
|
|
357
423
|
// ---------------------------------------------------------------------------
|
|
@@ -433,4 +499,19 @@ export const ROUTES: RouteDefinition[] = [
|
|
|
433
499
|
handler: handleCallbackRoutesList,
|
|
434
500
|
responseBody: CallbackRoutesListResponseSchema,
|
|
435
501
|
},
|
|
502
|
+
{
|
|
503
|
+
operationId: "platform_credits",
|
|
504
|
+
endpoint: "platform/credits",
|
|
505
|
+
method: "GET",
|
|
506
|
+
policy: {
|
|
507
|
+
requiredScopes: ["settings.read"],
|
|
508
|
+
allowedPrincipalTypes: ACTOR_PRINCIPALS,
|
|
509
|
+
},
|
|
510
|
+
summary: "Get the organization's remaining credit balance",
|
|
511
|
+
description:
|
|
512
|
+
"Fetches the org's settled, pending, and effective (remaining) credit balance in USD from the platform billing summary.",
|
|
513
|
+
tags: ["platform"],
|
|
514
|
+
handler: handlePlatformCredits,
|
|
515
|
+
responseBody: PlatformCreditsResponseSchema,
|
|
516
|
+
},
|
|
436
517
|
];
|
|
@@ -18,7 +18,7 @@ import { deliverChannelReply } from "./gateway-client.js";
|
|
|
18
18
|
import {
|
|
19
19
|
hasDeliverableAssistantText,
|
|
20
20
|
NO_RESPONSE_INLINE_RE,
|
|
21
|
-
} from "./
|
|
21
|
+
} from "./no-response.js";
|
|
22
22
|
import type { TaskProgressData } from "./slack-task-progress.js";
|
|
23
23
|
import {
|
|
24
24
|
getTaskProgressDataFromSurfaceData,
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import { existsSync, readFileSync, writeFileSync } from "node:fs";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
|
|
4
|
+
import type { WorkspaceMigration } from "./types.js";
|
|
5
|
+
|
|
6
|
+
// Repoint every `provider_connection` that referenced a per-provider
|
|
7
|
+
// `*-managed` connection onto the single provider-agnostic `vellum`
|
|
8
|
+
// connection.
|
|
9
|
+
//
|
|
10
|
+
// The five managed connections (anthropic/openai/gemini/fireworks/together)
|
|
11
|
+
// are collapsed into one `vellum` row and are no longer seeded (a follow-up
|
|
12
|
+
// migration deletes the orphaned rows), so any config still pointing at them
|
|
13
|
+
// would fail to resolve. Managed profiles also self-heal via the boot-time
|
|
14
|
+
// template reconcile, but `llm.default`, per-call-site overrides, and
|
|
15
|
+
// user-owned copies are rewritten here.
|
|
16
|
+
//
|
|
17
|
+
// Only the connection reference changes — each entry keeps its `provider` and
|
|
18
|
+
// `model`, which is how the `vellum` connection recovers the upstream at
|
|
19
|
+
// dispatch time. Names are hardcoded: this is a frozen historical snapshot.
|
|
20
|
+
|
|
21
|
+
const LEGACY_MANAGED_CONNECTIONS = new Set([
|
|
22
|
+
"anthropic-managed",
|
|
23
|
+
"openai-managed",
|
|
24
|
+
"gemini-managed",
|
|
25
|
+
"fireworks-managed",
|
|
26
|
+
"together-managed",
|
|
27
|
+
]);
|
|
28
|
+
|
|
29
|
+
const VELLUM_CONNECTION = "vellum";
|
|
30
|
+
|
|
31
|
+
export const repointManagedConnectionsToVellumMigration: WorkspaceMigration = {
|
|
32
|
+
id: "125-repoint-managed-connections-to-vellum",
|
|
33
|
+
description:
|
|
34
|
+
"Repoint provider_connection from the per-provider *-managed connections to the single vellum connection",
|
|
35
|
+
run(workspaceDir: string): void {
|
|
36
|
+
const configPath = join(workspaceDir, "config.json");
|
|
37
|
+
if (!existsSync(configPath)) return;
|
|
38
|
+
|
|
39
|
+
let config: Record<string, unknown>;
|
|
40
|
+
try {
|
|
41
|
+
const raw = JSON.parse(readFileSync(configPath, "utf-8"));
|
|
42
|
+
if (!raw || typeof raw !== "object" || Array.isArray(raw)) return;
|
|
43
|
+
config = raw as Record<string, unknown>;
|
|
44
|
+
} catch {
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const llm = readObject(config.llm);
|
|
49
|
+
if (llm === null) return;
|
|
50
|
+
|
|
51
|
+
let changed = false;
|
|
52
|
+
|
|
53
|
+
// llm.default
|
|
54
|
+
if (repointEntry(readObject(llm.default))) changed = true;
|
|
55
|
+
|
|
56
|
+
// llm.profiles.* (managed and user-owned alike — a repointed reference is
|
|
57
|
+
// correct regardless of who owns the profile)
|
|
58
|
+
const profiles = readObject(llm.profiles);
|
|
59
|
+
if (profiles !== null) {
|
|
60
|
+
for (const key of Object.keys(profiles)) {
|
|
61
|
+
if (repointEntry(readObject(profiles[key]))) changed = true;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// llm.callSites.*
|
|
66
|
+
const callSites = readObject(llm.callSites);
|
|
67
|
+
if (callSites !== null) {
|
|
68
|
+
for (const key of Object.keys(callSites)) {
|
|
69
|
+
if (repointEntry(readObject(callSites[key]))) changed = true;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
if (changed) {
|
|
74
|
+
config.llm = llm;
|
|
75
|
+
writeFileSync(configPath, JSON.stringify(config, null, 2) + "\n");
|
|
76
|
+
}
|
|
77
|
+
},
|
|
78
|
+
down(_workspaceDir: string): void {
|
|
79
|
+
// Forward-only.
|
|
80
|
+
},
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
/** Rewrite a single entry's provider_connection in place. Returns true if changed. */
|
|
84
|
+
function repointEntry(entry: Record<string, unknown> | null): boolean {
|
|
85
|
+
if (entry === null) return false;
|
|
86
|
+
const current = entry.provider_connection;
|
|
87
|
+
if (typeof current !== "string" || !LEGACY_MANAGED_CONNECTIONS.has(current)) {
|
|
88
|
+
return false;
|
|
89
|
+
}
|
|
90
|
+
entry.provider_connection = VELLUM_CONNECTION;
|
|
91
|
+
return true;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
function readObject(value: unknown): Record<string, unknown> | null {
|
|
95
|
+
if (value === null || typeof value !== "object" || Array.isArray(value)) {
|
|
96
|
+
return null;
|
|
97
|
+
}
|
|
98
|
+
return value as Record<string, unknown>;
|
|
99
|
+
}
|
|
@@ -122,6 +122,7 @@ import { seedDefaultUserGuardrailsMigration } from "./121-seed-default-user-guar
|
|
|
122
122
|
import { relocateDefaultUserBoundaryMigration } from "./122-relocate-default-user-boundary.js";
|
|
123
123
|
import { swapQualityProfileToFableMigration } from "./123-swap-quality-profile-to-fable.js";
|
|
124
124
|
import { correctDefaultUserBoundaryCommentsMigration } from "./124-correct-default-user-boundary-comments.js";
|
|
125
|
+
import { repointManagedConnectionsToVellumMigration } from "./125-repoint-managed-connections-to-vellum.js";
|
|
125
126
|
import { migrateToWorkspaceVolumeMigration } from "./migrate-to-workspace-volume.js";
|
|
126
127
|
import type { WorkspaceMigration } from "./types.js";
|
|
127
128
|
|
|
@@ -255,4 +256,5 @@ export const WORKSPACE_MIGRATIONS: WorkspaceMigration[] = [
|
|
|
255
256
|
relocateDefaultUserBoundaryMigration,
|
|
256
257
|
swapQualityProfileToFableMigration,
|
|
257
258
|
correctDefaultUserBoundaryCommentsMigration,
|
|
259
|
+
repointManagedConnectionsToVellumMigration,
|
|
258
260
|
];
|