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
2 changes: 1 addition & 1 deletion docs/api-reference/veryfront/provider.md
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@ Clear all registered model providers and reset lazy built-ins (for testing).
| `resolveVeryfrontCloudModelThinking` | Resolves Veryfront Cloud model thinking. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/provider/veryfront-cloud/model-catalog.ts) |
| `resolveVeryfrontCloudReasoningOption` | Resolves provider-neutral runtime reasoning for a Veryfront Cloud model. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/provider/veryfront-cloud/model-catalog.ts) |
| `resolveVeryfrontCloudThinkingProviderOptions` | Options accepted by resolve Veryfront Cloud thinking provider. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/provider/veryfront-cloud/model-catalog.ts) |
| `tryGetVeryfrontCloudProviderFromModelId` | | [source](https://github.com/veryfront/veryfront-code/blob/main/src/provider/veryfront-cloud/model-catalog.ts) |
| `tryGetVeryfrontCloudProviderFromModelId` | Return the Veryfront Cloud provider named by a model ID, including one this package does not list, or `undefined` when the ID names none. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/provider/veryfront-cloud/model-catalog.ts) |

### Types

Expand Down
47 changes: 6 additions & 41 deletions src/agent/runtime/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -115,13 +115,15 @@ import {
import { tryGetCacheKeyContext } from "#veryfront/cache/cache-key-builder.ts";
import type { ToolExecutionContext } from "#veryfront/tool";
import {
getModelRuntimeId,
getModelRuntimeProvider,
isLocalModelRuntime,
supportsModelRuntimeToolCalling,
} from "#veryfront/provider/runtime-inspection.ts";
import { generateText, streamText } from "#veryfront/runtime/runtime-bridge.ts";
import { resolveAgentSystem } from "./effective-agent-system.ts";
import {
resolveActiveProviderReplayProvider,
resolveRuntimeGenAiProviderName,
} from "./provider-replay-protocol.ts";
import {
attachOutputSchemaParser,
resolveAgentOutputSchema,
Expand Down Expand Up @@ -211,7 +213,6 @@ import {
createProviderReplayCheckpointEmissionState,
type ProviderReplayCheckpoint,
type ProviderReplayCheckpointEmissionState,
type ProviderReplayProvider,
} from "./provider-replay.ts";
import {
applySourceIntegrationPolicy,
Expand Down Expand Up @@ -299,42 +300,6 @@ export {

const NativeError = Error;

function getActiveProviderReplayProvider(
languageModel: ModelRuntime,
): ProviderReplayProvider | "unsupported" {
const modelRuntimeId = getModelRuntimeId(languageModel);
const provider =
(typeof languageModel.modelProvider === "string" ? languageModel.modelProvider : undefined) ??
getModelRuntimeProvider(languageModel) ??
(modelRuntimeId !== undefined
? resolveRuntimeGenAiProviderName(modelRuntimeId) ?? modelRuntimeId.split("/")[0]
: undefined);
if (provider === "anthropic") return "anthropic";
if (provider === "openai") return "openai-responses";
return "unsupported";
}

function resolveRuntimeGenAiProviderName(modelId: string): string | undefined {
const normalizedModelId = modelId.startsWith("veryfront-cloud/")
? modelId.slice("veryfront-cloud/".length)
: modelId;
const provider = normalizedModelId.split("/")[0]?.trim().toLowerCase();

switch (provider) {
case "anthropic":
return "anthropic";
case "openai":
return "openai";
case "google":
case "google-ai-studio":
return "gcp.gen_ai";
case "moonshotai":
return "moonshotai";
default:
return undefined;
}
}

export { enforceSkillPolicy, type SkillPolicyResult } from "./skill-policy-enforcement.ts";

import { DEFAULT_MAX_TOKENS, DEFAULT_TEMPERATURE, getModelMaxOutputTokens } from "./constants.ts";
Expand Down Expand Up @@ -2828,7 +2793,7 @@ export class AgentRuntime {
applyProviderReplayCheckpointsToMessages(
currentMessages,
getRuntimeProviderReplayCheckpoints(this.config),
{ activeProvider: getActiveProviderReplayProvider(languageModel) },
{ activeProvider: resolveActiveProviderReplayProvider(languageModel) },
);
const totalUsage = { promptTokens: 0, completionTokens: 0, totalTokens: 0 };

Expand Down Expand Up @@ -3537,7 +3502,7 @@ export class AgentRuntime {
applyProviderReplayCheckpointsToMessages(
currentMessages,
getRuntimeProviderReplayCheckpoints(this.config),
{ activeProvider: getActiveProviderReplayProvider(languageModel) },
{ activeProvider: resolveActiveProviderReplayProvider(languageModel) },
);
const totalUsage = { promptTokens: 0, completionTokens: 0, totalTokens: 0 };

Expand Down
52 changes: 52 additions & 0 deletions src/agent/runtime/provider-replay-protocol.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import { assertEquals } from "#veryfront/testing/assert.ts";
import { describe, it } from "#veryfront/testing/bdd.ts";
import type { ModelRuntime } from "#veryfront/provider/types.ts";
import { registerVeryfrontCloudModelFacts } from "#veryfront/provider/veryfront-cloud/model-catalog.ts";
import {
resolveActiveProviderReplayProvider,
resolveRuntimeGenAiProviderName,
} from "./provider-replay-protocol.ts";

function model(modelProvider: string, surface?: string): ModelRuntime {
const runtime: ModelRuntime = {
provider: "veryfront-cloud",
modelProvider,
modelId: "m1",
doGenerate: () => Promise.reject(new Error("not called")),
doStream: () => Promise.reject(new Error("not called")),
};
if (surface !== undefined) {
registerVeryfrontCloudModelFacts(runtime, () =>
({
provider: modelProvider,
surface,
native: false,
transportPlan: "chat-completions",
}) as never);
}
return runtime;
}

describe("resolveActiveProviderReplayProvider", () => {
it("replays the named protocol providers as before", () => {
assertEquals(resolveActiveProviderReplayProvider(model("anthropic")), "anthropic");
assertEquals(resolveActiveProviderReplayProvider(model("openai")), "openai-responses");
});

it("replays a provider served on the Anthropic surface like anthropic/*", () => {
assertEquals(resolveActiveProviderReplayProvider(model("acme-labs", "anthropic")), "anthropic");
});

it("does not replay a provider on another surface, or one with no served facts", () => {
assertEquals(resolveActiveProviderReplayProvider(model("acme-labs", "openai")), "unsupported");
assertEquals(resolveActiveProviderReplayProvider(model("acme-labs")), "unsupported");
});
});

describe("resolveRuntimeGenAiProviderName", () => {
it("names the GenAI provider of a runtime model id", () => {
assertEquals(resolveRuntimeGenAiProviderName("veryfront-cloud/anthropic/claude"), "anthropic");
assertEquals(resolveRuntimeGenAiProviderName("google-ai-studio/gemini"), "gcp.gen_ai");
assertEquals(resolveRuntimeGenAiProviderName("acme-labs/m1"), undefined);
});
});
54 changes: 54 additions & 0 deletions src/agent/runtime/provider-replay-protocol.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
/**
* Which provider-replay protocol, and which GenAI provider name, a runtime
* model maps to. A Veryfront Cloud model of a provider served on the Anthropic
* surface replays like `anthropic/*`: the replay format belongs to the wire
* protocol, not to the vendor.
*/
import {
getModelRuntimeId,
getModelRuntimeProvider,
} from "#veryfront/provider/runtime-inspection.ts";
import type { ModelRuntime } from "#veryfront/provider/types.ts";
import { readVeryfrontCloudModelFacts } from "#veryfront/provider/veryfront-cloud/model-catalog.ts";
import type { ProviderReplayProvider } from "./provider-replay.ts";

/** The provider-replay protocol a model's turns are replayed with. */
export function resolveActiveProviderReplayProvider(
languageModel: ModelRuntime,
): ProviderReplayProvider | "unsupported" {
const modelRuntimeId = getModelRuntimeId(languageModel);
const provider =
(typeof languageModel.modelProvider === "string" ? languageModel.modelProvider : undefined) ??
getModelRuntimeProvider(languageModel) ??
(modelRuntimeId !== undefined
? resolveRuntimeGenAiProviderName(modelRuntimeId) ?? modelRuntimeId.split("/")[0]
: undefined);
if (provider === "anthropic") return "anthropic";
if (provider === "openai") return "openai-responses";
// A Veryfront Cloud model of a provider served on the Anthropic surface
// replays Anthropic thinking and tool blocks like `anthropic/*`.
if (readVeryfrontCloudModelFacts(languageModel)?.surface === "anthropic") return "anthropic";
return "unsupported";
}

/** The GenAI semantic-convention provider name for a runtime model id. */
export function resolveRuntimeGenAiProviderName(modelId: string): string | undefined {
const normalizedModelId = modelId.startsWith("veryfront-cloud/")
? modelId.slice("veryfront-cloud/".length)
: modelId;
const provider = normalizedModelId.split("/")[0]?.trim().toLowerCase();

switch (provider) {
case "anthropic":
return "anthropic";
case "openai":
return "openai";
case "google":
case "google-ai-studio":
return "gcp.gen_ai";
case "moonshotai":
return "moonshotai";
default:
return undefined;
}
}
61 changes: 60 additions & 1 deletion src/agent/runtime/provider-tool-compat.test.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
import { assertEquals, assertStrictEquals } from "#veryfront/testing/assert.ts";
import { describe, it } from "#veryfront/testing/bdd.ts";
import { afterEach, describe, it } from "#veryfront/testing/bdd.ts";
import {
__resetVeryfrontCloudCatalogForTests,
__setVeryfrontCloudCatalogForTests,
} from "#veryfront/provider/veryfront-cloud/catalog-client.ts";
import type { ToolDefinition } from "#veryfront/tool";
import {
createMoonshotSchemaExpansionBudget,
Expand Down Expand Up @@ -571,3 +575,58 @@ describe("provider-tool-compat", () => {
});
});
});

describe("getProviderToolProfile for providers only the served catalog lists", () => {
afterEach(__resetVeryfrontCloudCatalogForTests);

const served = (surface: string) =>
__setVeryfrontCloudCatalogForTests({
models: [{
id: "m1",
modelId: "acme-labs/m1",
provider: "acme-labs",
surface,
operations: [surface === "anthropic" ? "messages" : "chat-completions"],
aliases: [],
capabilities: {},
}],
});

it("applies the schema rules of the surface the provider is served on", () => {
served("google");
assertEquals(getProviderToolProfile("veryfront-cloud/acme-labs/m1"), {
provider: "google",
sanitizeSchema: true,
});
served("anthropic");
assertEquals(getProviderToolProfile("veryfront-cloud/acme-labs/m1"), {
provider: "anthropic",
sanitizeSchema: true,
});
});

it("applies the served surface before the kimi- model-name heuristic", () => {
__setVeryfrontCloudCatalogForTests({
models: [{
id: "kimi-1",
modelId: "acme-labs/kimi-1",
provider: "acme-labs",
surface: "google",
operations: ["chat-completions"],
aliases: [],
capabilities: {},
}],
});
assertEquals(getProviderToolProfile("veryfront-cloud/acme-labs/kimi-1").provider, "google");
// The heuristic still applies where no served surface decides.
assertEquals(getProviderToolProfile("kimi-k2.6").provider, "moonshot");
assertEquals(getProviderToolProfile("moonshotai/kimi-k2.6").provider, "moonshot");
});

it("keeps an unlisted provider on the OpenAI surface, and a direct id, unknown", () => {
served("openai");
assertEquals(getProviderToolProfile("veryfront-cloud/acme-labs/m1").provider, "unknown");
served("anthropic");
assertEquals(getProviderToolProfile("acme-labs/m1").provider, "unknown");
});
});
17 changes: 16 additions & 1 deletion src/agent/runtime/provider-tool-compat.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { resolveVeryfrontCloudModelSurface } from "#veryfront/provider/veryfront-cloud/model-catalog.ts";
import { privateJsonStringify } from "#veryfront/security/private-json.ts";
import type { ToolDefinition } from "#veryfront/tool";
import type { JsonSchema } from "#veryfront/tool/schema";
Expand Down Expand Up @@ -97,7 +98,21 @@ export function getProviderToolProfile(model?: string): ProviderToolProfile {
return { provider: "anthropic", sanitizeSchema: true };
}

if (provider === "moonshot" || provider === "moonshotai" || modelName?.startsWith("kimi-")) {
if (provider === "moonshot" || provider === "moonshotai") {
return { provider: "moonshot", sanitizeSchema: true };
}

// A Veryfront Cloud provider this list does not name takes the schema rules
// of the wire protocol it is served on: Google and Anthropic constrain tool
// schemas by protocol, not by vendor. This runs before the model-name
// heuristic below, so a served provider's own naming cannot override it.
if (parts[0] === "veryfront-cloud") {
const surface = resolveVeryfrontCloudModelSurface(normalized);
Comment thread
kojiwakayama marked this conversation as resolved.
if (surface === "google") return { provider: "google", sanitizeSchema: true };
if (surface === "anthropic") return { provider: "anthropic", sanitizeSchema: true };
}

if (modelName?.startsWith("kimi-")) {
return { provider: "moonshot", sanitizeSchema: true };
}

Expand Down
17 changes: 14 additions & 3 deletions src/provider/veryfront-cloud/model-catalog.ts
Original file line number Diff line number Diff line change
Expand Up @@ -726,17 +726,28 @@ export function getVeryfrontCloudProviderFromModelId(
});
}

/** Return the Veryfront Cloud provider named by a model ID, including one this package does not list, or `undefined` when the ID names none. */
/**
* The wire surface a Veryfront Cloud model ID's provider is served on, or
* `undefined` when the ID names no provider. A newly served provider reports
* the surface the served catalog gives it, not only the protocol-named ones.
*/
export function resolveVeryfrontCloudModelSurface(
modelId: string,
): VeryfrontCloudSurfaceId | undefined {
const provider = tryGetVeryfrontCloudProviderFromModelId(modelId);
return provider === undefined ? undefined : resolveVeryfrontCloudSurface(provider);
}

/**
* Whether a Veryfront Cloud model ID speaks the Anthropic protocol: its
* provider is served on the Anthropic surface. A newly served provider on that
* surface counts, not only `anthropic/*`.
*/
export function isVeryfrontCloudAnthropicSurfaceModel(modelId: string): boolean {
const provider = tryGetVeryfrontCloudProviderFromModelId(modelId);
return provider !== undefined && resolveVeryfrontCloudSurface(provider) === "anthropic";
return resolveVeryfrontCloudModelSurface(modelId) === "anthropic";
}

/** Return the Veryfront Cloud provider named by a model ID, including one this package does not list, or `undefined` when the ID names none. */
export function tryGetVeryfrontCloudProviderFromModelId(
modelId: string,
): VeryfrontCloudProviderId | undefined {
Expand Down
Loading