mirror of
https://github.com/openclaw/openclaw.git
synced 2026-08-08 19:12:22 +00:00
fix(foundry): use bearer auth for Claude
This commit is contained in:
@@ -377,6 +377,16 @@ describe("microsoft-foundry plugin", () => {
|
||||
);
|
||||
});
|
||||
|
||||
it("requests scoped Azure CLI tokens for Foundry Anthropic probes", async () => {
|
||||
mockAzureCliTokenRaw(JSON.stringify({ accessToken: "scoped-token" }));
|
||||
|
||||
await getAccessTokenResultAsync({ scope: FOUNDRY_ANTHROPIC_SCOPE });
|
||||
|
||||
expect(execFileMock.mock.calls[0]?.[1]).toEqual(
|
||||
expect.arrayContaining(["--scope", FOUNDRY_ANTHROPIC_SCOPE]),
|
||||
);
|
||||
});
|
||||
|
||||
it("fails clearly when the selected Azure subscription is not in the enabled list", async () => {
|
||||
const provider = registerProvider();
|
||||
execFileSyncMock.mockImplementation((_file: string, args: string[]) => {
|
||||
@@ -744,6 +754,23 @@ describe("microsoft-foundry plugin", () => {
|
||||
expect(model?.compat?.supportsReasoningEffort).toBe(true);
|
||||
});
|
||||
|
||||
it("preserves Fable limits when adding a newly selected Foundry deployment", async () => {
|
||||
const provider = registerProvider();
|
||||
const config = buildFoundryConfig({ models: [] });
|
||||
|
||||
await provider.onModelSelected?.({
|
||||
config,
|
||||
model: "microsoft-foundry/claude-fable-5",
|
||||
prompter: {} as never,
|
||||
agentDir: "/tmp/test-agent",
|
||||
});
|
||||
|
||||
const model = config.models?.providers?.["microsoft-foundry"]?.models[0];
|
||||
expect(model?.id).toBe("claude-fable-5");
|
||||
expect(model?.contextWindow).toBe(1_000_000);
|
||||
expect(model?.maxTokens).toBe(128_000);
|
||||
});
|
||||
|
||||
it("accepts tenant domains as valid tenant identifiers", () => {
|
||||
expect(isValidTenantIdentifier("contoso.onmicrosoft.com")).toBe(true);
|
||||
expect(isValidTenantIdentifier("00000000-0000-0000-0000-000000000000")).toBe(true);
|
||||
|
||||
@@ -26,6 +26,7 @@ import {
|
||||
requiresFoundryMaxCompletionTokens,
|
||||
DEFAULT_API,
|
||||
DEFAULT_GPT5_API,
|
||||
FOUNDRY_ANTHROPIC_SCOPE,
|
||||
usesFoundryResponsesByDefault,
|
||||
} from "./shared.js";
|
||||
|
||||
@@ -529,6 +530,7 @@ export async function testFoundryConnection(params: {
|
||||
}): Promise<void> {
|
||||
try {
|
||||
const { accessToken } = getAccessTokenResult({
|
||||
scope: params.api === ANTHROPIC_MESSAGES_API ? FOUNDRY_ANTHROPIC_SCOPE : undefined,
|
||||
subscriptionId: params.subscriptionId,
|
||||
tenantId: params.tenantId,
|
||||
});
|
||||
|
||||
@@ -148,8 +148,8 @@ export function buildMicrosoftFoundryProvider(): ProviderPlugin {
|
||||
: {}),
|
||||
input: selectedModelCapabilities.input,
|
||||
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
|
||||
contextWindow: 128_000,
|
||||
maxTokens: 16_384,
|
||||
contextWindow: selectedModelCapabilities.contextWindow,
|
||||
maxTokens: selectedModelCapabilities.maxTokens,
|
||||
...(selectedModelCapabilities.compat ? { compat: selectedModelCapabilities.compat } : {}),
|
||||
});
|
||||
}
|
||||
|
||||
@@ -83,6 +83,31 @@ describe("Anthropic provider", () => {
|
||||
expect(config.defaultHeaders?.["cf-aig-authorization"]).toBe("Bearer gateway-token");
|
||||
});
|
||||
|
||||
it("uses bearer auth for Microsoft Foundry Anthropic requests", async () => {
|
||||
const model = makeAnthropicModel({
|
||||
provider: "microsoft-foundry",
|
||||
baseUrl: "https://example.services.ai.azure.com/anthropic",
|
||||
});
|
||||
const context = {
|
||||
messages: [{ role: "user", content: "hello", timestamp: 1 }],
|
||||
} satisfies Context;
|
||||
|
||||
streamAnthropic(model, context, {
|
||||
apiKey: "entra-access-token",
|
||||
});
|
||||
|
||||
await vi.waitFor(() => expect(anthropicMockState.configs).toHaveLength(1));
|
||||
const config = anthropicMockState.configs[0] as {
|
||||
apiKey?: string | null;
|
||||
authToken?: string | null;
|
||||
defaultHeaders?: Record<string, string | null>;
|
||||
};
|
||||
|
||||
expect(config.apiKey).toBeNull();
|
||||
expect(config.authToken).toBe("entra-access-token");
|
||||
expect(config.defaultHeaders?.["x-api-key"]).toBeUndefined();
|
||||
});
|
||||
|
||||
it("preserves provider-signed Anthropic thinking and drops reasoning_content placeholders", async () => {
|
||||
const highSurrogate = String.fromCharCode(0xd83d);
|
||||
const signedThinking = `keep${highSurrogate}signed`;
|
||||
|
||||
@@ -945,6 +945,27 @@ function createClient(
|
||||
return { client, isOAuthToken: false };
|
||||
}
|
||||
|
||||
if (model.provider === "microsoft-foundry") {
|
||||
const client = new Anthropic({
|
||||
apiKey: null,
|
||||
authToken: apiKey,
|
||||
baseURL: model.baseUrl,
|
||||
dangerouslyAllowBrowser: true,
|
||||
defaultHeaders: mergeHeaders(
|
||||
{
|
||||
accept: "application/json",
|
||||
"anthropic-dangerous-direct-browser-access": "true",
|
||||
...(betaFeatures.length > 0 ? { "anthropic-beta": betaFeatures.join(",") } : {}),
|
||||
},
|
||||
model.headers,
|
||||
dynamicHeaders,
|
||||
optionsHeaders,
|
||||
),
|
||||
});
|
||||
|
||||
return { client, isOAuthToken: false };
|
||||
}
|
||||
|
||||
// OAuth: Bearer auth, Claude Code identity headers
|
||||
if (isOAuthToken(apiKey)) {
|
||||
const client = new Anthropic({
|
||||
|
||||
Reference in New Issue
Block a user