Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 38 additions & 0 deletions assistant/openapi.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -13702,6 +13702,44 @@ paths:
required:
- sourceText
additionalProperties: false
/v1/skills/import:
post:
operationId: skills_import_post
summary: Import skill from file
description: Import a custom skill by uploading a ZIP or tar.gz archive containing a SKILL.md file.
tags:
- skills
responses:
"200":
description: Successful response
content:
application/json:
schema:
type: object
properties:
ok:
type: boolean
skillId:
type: string
required:
- ok
- skillId
additionalProperties: false
requestBody:
required: true
content:
application/json:
schema:
type: object
properties:
fileName:
type: string
fileContent:
type: string
required:
- fileName
- fileContent
additionalProperties: false
/v1/skills/install:
post:
operationId: skills_install_post
Expand Down
4 changes: 2 additions & 2 deletions assistant/scripts/sync-llm-catalog.ts
Original file line number Diff line number Diff line change
Expand Up @@ -97,8 +97,8 @@ function projectProvider(entry: ProviderCatalogEntry): Record<string, unknown> {
projected.apiKeyPlaceholder = entry.apiKeyPlaceholder;
if (entry.credentialsGuide !== undefined)
projected.credentialsGuide = entry.credentialsGuide;
if (entry.supportsManagedAuth !== undefined)
projected.supportsManagedAuth = entry.supportsManagedAuth;
if (entry.supportsPlatformAuth !== undefined)
projected.supportsPlatformAuth = entry.supportsPlatformAuth;
projected.defaultModel = entry.defaultModel;
projected.models = entry.models.map(projectModel);
// NOTE: `apiKeyUrl` intentionally omitted — clients use
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -189,7 +189,7 @@ describe("Invariant 2: no generic plaintext secret read API", () => {
"messaging/providers/slack/adapter.ts", // Slack bot token lookup for Socket Mode connectivity check
"credential-health/credential-health-service.ts", // proactive credential health monitoring
"daemon/handlers/config-slack-channel.ts", // Slack channel config credential management
"providers/managed-proxy/context.ts", // managed proxy API key lookup for provider initialization
"providers/platform-proxy/context.ts", // managed proxy API key lookup for provider initialization
"platform/client.ts", // platform client credential store fallback for standalone CLI auth
"mcp/mcp-oauth-provider.ts", // MCP OAuth token/client/discovery persistence
"runtime/routes/integrations/slack/token.ts", // shared Slack token resolver (bot/user token lookup for CLI use routes)
Expand Down
2 changes: 1 addition & 1 deletion assistant/src/__tests__/image-credentials.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ mock.module("../security/secure-keys.js", () => ({
getProviderKeyAsync: async (_provider: string) => mockProviderKey,
}));

mock.module("../providers/managed-proxy/context.js", () => ({
mock.module("../providers/platform-proxy/context.js", () => ({
resolveManagedProxyContext: async () => ({
enabled: !!mockPlatformBaseUrl && !!mockAssistantApiKey,
platformBaseUrl: mockPlatformBaseUrl,
Expand Down
2 changes: 1 addition & 1 deletion assistant/src/__tests__/inference-no-mode-boot-e2e.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ mock.module("../security/secure-keys.js", () => ({
}));

// Managed proxy context — always unavailable in this test (no platform auth).
mock.module("../providers/managed-proxy/context.js", () => ({
mock.module("../providers/platform-proxy/context.js", () => ({
buildManagedBaseUrl: async () => null,
resolveManagedProxyContext: async () => {
throw new Error("managed proxy not available in test");
Expand Down
18 changes: 9 additions & 9 deletions assistant/src/__tests__/llm-catalog-parity.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@ import { readFileSync } from "node:fs";
import { join } from "node:path";
import { describe, expect, test } from "bun:test";

import { MANAGED_PROVIDER_META } from "../providers/managed-proxy/constants.js";
import { PROVIDER_CATALOG } from "../providers/model-catalog.js";
import { PLATFORM_PROVIDER_META } from "../providers/platform-proxy/constants.js";
import { resolvePricing, resolvePricingForUsage } from "../util/pricing.js";

/**
Expand Down Expand Up @@ -84,7 +84,7 @@ interface ClientCatalogEntry {
envVar?: string;
apiKeyPlaceholder?: string;
credentialsGuide?: ClientCatalogCredentialsGuide;
supportsManagedAuth?: boolean;
supportsPlatformAuth?: boolean;
defaultModel: string;
models: ClientCatalogModel[];
}
Expand Down Expand Up @@ -136,8 +136,8 @@ describe("LLM catalog parity: daemon vs client", () => {
expect(clientEntry.setupHint).toBe(daemonEntry.setupHint);
expect(clientEntry.envVar).toBe(daemonEntry.envVar);
expect(clientEntry.apiKeyPlaceholder).toBe(daemonEntry.apiKeyPlaceholder);
expect(clientEntry.supportsManagedAuth).toBe(
daemonEntry.supportsManagedAuth,
expect(clientEntry.supportsPlatformAuth).toBe(
daemonEntry.supportsPlatformAuth,
);
expect(clientEntry.credentialsGuide).toEqual(
daemonEntry.credentialsGuide,
Expand All @@ -146,16 +146,16 @@ describe("LLM catalog parity: daemon vs client", () => {
}
});

test("supportsManagedAuth mirrors MANAGED_PROVIDER_META", () => {
// The catalog field is derived from MANAGED_PROVIDER_META at build
test("supportsPlatformAuth mirrors PLATFORM_PROVIDER_META", () => {
// The catalog field is derived from PLATFORM_PROVIDER_META at build
// time. This test guards against future hand-edits to model-catalog.ts
// that would let the two drift. Adding a provider to MANAGED_PROVIDER_META
// that would let the two drift. Adding a provider to PLATFORM_PROVIDER_META
// must auto-propagate; flipping `managed: true` to `false` (or vice
// versa) must propagate too.
for (const entry of PROVIDER_CATALOG) {
const expectedSupportsManagedAuth =
MANAGED_PROVIDER_META[entry.id]?.managed === true;
expect(entry.supportsManagedAuth).toBe(expectedSupportsManagedAuth);
PLATFORM_PROVIDER_META[entry.id]?.managed === true;
expect(entry.supportsPlatformAuth).toBe(expectedSupportsManagedAuth);
}
});

Expand Down
2 changes: 1 addition & 1 deletion assistant/src/__tests__/media-generate-image.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ let mockManagedProxyContext = {
assistantApiKey: "",
};

mock.module("../providers/managed-proxy/context.js", () => ({
mock.module("../providers/platform-proxy/context.js", () => ({
buildManagedBaseUrl: async () => mockManagedBaseUrl,
resolveManagedProxyContext: async () => mockManagedProxyContext,
}));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ import {
hasManagedProxyPrereqs,
managedFallbackEnabledFor,
resolveManagedProxyContext,
} from "../providers/managed-proxy/context.js";
} from "../providers/platform-proxy/context.js";

describe("resolveManagedProxyContext", () => {
beforeEach(() => {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { beforeEach, describe, expect, mock, test } from "bun:test";

import { MANAGED_PROVIDER_META } from "../providers/managed-proxy/constants.js";
import { PLATFORM_PROVIDER_META } from "../providers/platform-proxy/constants.js";
import { credentialKey } from "../security/credential-key.js";

// ---------------------------------------------------------------------------
Expand Down Expand Up @@ -436,7 +436,7 @@ describe("managed proxy integration — ollama exclusion", () => {
});

test("ollama metadata is marked as non-managed", () => {
const meta = MANAGED_PROVIDER_META.ollama;
const meta = PLATFORM_PROVIDER_META.ollama;
expect(meta).toBeDefined();
expect(meta.managed).toBe(false);
expect(meta.proxyPath).toBeUndefined();
Expand Down Expand Up @@ -478,7 +478,7 @@ describe("config mode flip → provider reinit", () => {
describe("managed proxy integration — constants integrity", () => {
test("anthropic, openai, and gemini have metadata with managed=true and a proxyPath", () => {
for (const provider of ["anthropic", "openai", "gemini"]) {
const meta = MANAGED_PROVIDER_META[provider];
const meta = PLATFORM_PROVIDER_META[provider];
expect(meta).toBeDefined();
expect(meta.managed).toBe(true);
expect(meta.proxyPath).toBeTruthy();
Expand All @@ -487,27 +487,27 @@ describe("managed proxy integration — constants integrity", () => {
});

test("anthropic routes through anthropic proxy path", () => {
expect(MANAGED_PROVIDER_META.anthropic.proxyPath).toBe(
expect(PLATFORM_PROVIDER_META.anthropic.proxyPath).toBe(
"/v1/runtime-proxy/anthropic",
);
});

test("gemini routes through gemini proxy path", () => {
expect(MANAGED_PROVIDER_META.gemini.proxyPath).toBe(
expect(PLATFORM_PROVIDER_META.gemini.proxyPath).toBe(
"/v1/runtime-proxy/gemini",
);
});

test("openai routes through openai proxy path", () => {
expect(MANAGED_PROVIDER_META.openai.proxyPath).toBe(
expect(PLATFORM_PROVIDER_META.openai.proxyPath).toBe(
"/v1/runtime-proxy/openai",
);
});

test("fireworks and openrouter are not managed proxy capable", () => {
for (const provider of ["fireworks", "openrouter"]) {
expect(MANAGED_PROVIDER_META[provider].managed).toBe(false);
expect(MANAGED_PROVIDER_META[provider].proxyPath).toBeUndefined();
expect(PLATFORM_PROVIDER_META[provider].managed).toBe(false);
expect(PLATFORM_PROVIDER_META[provider].proxyPath).toBeUndefined();
}
});
});
2 changes: 1 addition & 1 deletion assistant/src/daemon/lifecycle.ts
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ import { loadUserPlugins } from "../plugins/user-loader.js";
import { backfillGuardIfNeeded } from "../proactive-artifact/index.js";
import { ensurePromptFiles } from "../prompts/system-prompt.js";
import { runProviderConnectionsBackfill } from "../providers/inference/backfill.js";
import { resolveManagedProxyContext } from "../providers/managed-proxy/context.js";
import { resolveManagedProxyContext } from "../providers/platform-proxy/context.js";
import { broadcastMessage } from "../runtime/assistant-event-hub.js";
import {
initAuthSigningKey,
Expand Down
6 changes: 3 additions & 3 deletions assistant/src/media/image-credentials.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@
* credentials are unavailable.
*/

import { MANAGED_PROVIDER_META } from "../providers/managed-proxy/constants.js";
import { resolveManagedProxyContext } from "../providers/managed-proxy/context.js";
import { PLATFORM_PROVIDER_META } from "../providers/platform-proxy/constants.js";
import { resolveManagedProxyContext } from "../providers/platform-proxy/context.js";
import { getProviderKeyAsync } from "../security/secure-keys.js";
import type { ImageGenCredentials, ImageGenProvider } from "./types.js";

Expand All @@ -33,7 +33,7 @@ export async function resolveImageGenCredentials(opts: {
// Resolve platform URL + assistant API key from a single snapshot so
// baseUrl and assistantApiKey can't diverge if the credential is cleared
// between lookups.
const meta = MANAGED_PROVIDER_META[provider];
const meta = PLATFORM_PROVIDER_META[provider];
const ctx = await resolveManagedProxyContext();
if (
!meta?.managed ||
Expand Down
8 changes: 4 additions & 4 deletions assistant/src/memory/embedding-backend.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@ import { isAssistantFeatureFlagEnabled } from "../config/assistant-feature-flags
import { getOllamaBaseUrlEnv } from "../config/env.js";
import { resolveCallSiteConfig } from "../config/llm-resolver.js";
import type { AssistantConfig } from "../config/types.js";
import { MANAGED_PROVIDER_META } from "../providers/managed-proxy/constants.js";
import { resolveManagedProxyContext } from "../providers/managed-proxy/context.js";
import { PLATFORM_PROVIDER_META } from "../providers/platform-proxy/constants.js";
import { resolveManagedProxyContext } from "../providers/platform-proxy/context.js";
import { getProviderKeyAsync } from "../security/secure-keys.js";
import { getLogger } from "../util/logger.js";
import { GeminiEmbeddingBackend } from "./embedding-gemini.js";
Expand Down Expand Up @@ -311,7 +311,7 @@ export async function selectEmbeddingBackend(
) {
const proxyCtx = await resolveManagedProxyContext();
if (proxyCtx.enabled) {
const meta = MANAGED_PROVIDER_META["gemini"];
const meta = PLATFORM_PROVIDER_META["gemini"];
if (meta?.managed && meta.proxyPath) {
const managedBaseUrl = `${proxyCtx.platformBaseUrl}${meta.proxyPath}`;
const managedModel = config.memory.embeddings.geminiModel;
Expand Down Expand Up @@ -685,7 +685,7 @@ async function selectFallbackBackends(
) {
// Try managed proxy Gemini as fallback when no direct key exists.
const proxyCtx = await resolveManagedProxyContext();
const meta = MANAGED_PROVIDER_META["gemini"];
const meta = PLATFORM_PROVIDER_META["gemini"];
if (proxyCtx.enabled && meta?.managed && meta.proxyPath) {
const managedBaseUrl = `${proxyCtx.platformBaseUrl}${meta.proxyPath}`;
const managedModel = config.memory.embeddings.geminiModel;
Expand Down
2 changes: 1 addition & 1 deletion assistant/src/platform/client.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ let mockAssistantId = "";
// Module mocks
// ---------------------------------------------------------------------------

mock.module("../providers/managed-proxy/context.js", () => ({
mock.module("../providers/platform-proxy/context.js", () => ({
resolveManagedProxyContext: async () => mockManagedProxyCtx,
}));

Expand Down
2 changes: 1 addition & 1 deletion assistant/src/platform/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
*/

import { getPlatformAssistantId } from "../config/env.js";
import { resolveManagedProxyContext } from "../providers/managed-proxy/context.js";
import { resolveManagedProxyContext } from "../providers/platform-proxy/context.js";
import { credentialKey } from "../security/credential-key.js";
import { getSecureKeyAsync } from "../security/secure-keys.js";
import { getLogger } from "../util/logger.js";
Expand Down
2 changes: 1 addition & 1 deletion assistant/src/providers/inference/resolve-auth.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
import {
buildManagedBaseUrl,
resolveManagedProxyContext,
} from "../../providers/managed-proxy/context.js";
} from "../../providers/platform-proxy/context.js";
import { getSecureKeyAsync } from "../../security/secure-keys.js";
import type { Auth, ResolvedAuth } from "./auth.js";

Expand Down
12 changes: 6 additions & 6 deletions assistant/src/providers/model-catalog.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { MANAGED_PROVIDER_META } from "./managed-proxy/constants.js";
import { PLATFORM_PROVIDER_META } from "./platform-proxy/constants.js";

export type LongContextMode =
| "native-model"
Expand Down Expand Up @@ -91,12 +91,12 @@ export interface ProviderCatalogEntry {
/**
* Whether this provider supports the `platform` auth type (Vellum-managed
* keys routed through the platform proxy). Derived from
* `MANAGED_PROVIDER_META` at catalog build time so the two stay in lock
* `PLATFORM_PROVIDER_META` at catalog build time so the two stay in lock
* step. Clients use this field to hide the "Platform (managed by Vellum)"
* option from the auth-type dropdown for providers like Fireworks or
* OpenRouter where managed keys are not available.
*/
supportsManagedAuth?: boolean;
supportsPlatformAuth?: boolean;
}

/**
Expand Down Expand Up @@ -874,11 +874,11 @@ export const PROVIDER_CATALOG: ProviderCatalogEntry[] =
RAW_PROVIDER_CATALOG.map((entry) => ({
...entry,
models: entry.models.map(catalogModel),
// Derive supportsManagedAuth from MANAGED_PROVIDER_META so the catalog
// Derive supportsPlatformAuth from PLATFORM_PROVIDER_META so the catalog
// and the proxy routing table can never drift. Adding a provider to
// MANAGED_PROVIDER_META with `managed: true` automatically opts it into
// PLATFORM_PROVIDER_META with `managed: true` automatically opts it into
// the Platform auth-type dropdown in the clients.
supportsManagedAuth: MANAGED_PROVIDER_META[entry.id]?.managed === true,
supportsPlatformAuth: PLATFORM_PROVIDER_META[entry.id]?.managed === true,
}));

/** Check if a model ID is in the catalog for a given provider. */
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ export interface ManagedProviderMeta {
* managed credentials are present; that policy lives in the registry/context
* fallback allowlists.
*/
export const MANAGED_PROVIDER_META: Record<string, ManagedProviderMeta> = {
export const PLATFORM_PROVIDER_META: Record<string, ManagedProviderMeta> = {
openai: {
name: "openai",
managed: true,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
import { getPlatformBaseUrl } from "../../config/env.js";
import { credentialKey } from "../../security/credential-key.js";
import { getSecureKeyAsync } from "../../security/secure-keys.js";
import { MANAGED_PROVIDER_META } from "./constants.js";
import { PLATFORM_PROVIDER_META } from "./constants.js";

/** Storage key for the assistant API key credential. */
const ASSISTANT_API_KEY_STORAGE_KEY = credentialKey(
Expand Down Expand Up @@ -70,7 +70,7 @@ export async function hasManagedProxyPrereqs(): Promise<boolean> {
export async function buildManagedBaseUrl(
provider: string,
): Promise<string | undefined> {
const meta = MANAGED_PROVIDER_META[provider];
const meta = PLATFORM_PROVIDER_META[provider];
if (!meta?.managed || !meta.proxyPath) return undefined;

const ctx = await resolveManagedProxyContext();
Expand All @@ -88,7 +88,7 @@ export async function buildManagedBaseUrl(
export async function managedFallbackEnabledFor(
provider: string,
): Promise<boolean> {
const meta = MANAGED_PROVIDER_META[provider];
const meta = PLATFORM_PROVIDER_META[provider];
if (!meta?.managed) return false;
return await hasManagedProxyPrereqs();
}
2 changes: 1 addition & 1 deletion assistant/src/providers/provider-availability.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@

import { API_KEY_PROVIDERS } from "../config/loader.js";
import { getProviderKeyAsync } from "../security/secure-keys.js";
import { managedFallbackEnabledFor } from "./managed-proxy/context.js";
import { managedFallbackEnabledFor } from "./platform-proxy/context.js";

/**
* Check whether a single provider is usable — via a user-provided key
Expand Down
6 changes: 3 additions & 3 deletions assistant/src/providers/registry.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,12 @@ import {
// ---------------------------------------------------------------------------
import type { ProviderConnection } from "./inference/auth.js";
import { resolveAuth } from "./inference/resolve-auth.js";
import { isModelInCatalog, PROVIDER_CATALOG } from "./model-catalog.js";
import { getProviderDefaultModel } from "./model-intents.js";
import {
buildManagedBaseUrl,
resolveManagedProxyContext,
} from "./managed-proxy/context.js";
import { isModelInCatalog, PROVIDER_CATALOG } from "./model-catalog.js";
import { getProviderDefaultModel } from "./model-intents.js";
} from "./platform-proxy/context.js";
import { RetryProvider } from "./retry.js";
import type { Provider } from "./types.js";
import { UsageTrackingProvider } from "./usage-tracking.js";
Expand Down
Loading
Loading