mirror of
https://github.com/badlogic/pi-mono.git
synced 2026-11-05 23:37:25 +00:00
parent
7df73a00c6
commit
24e5cc04f4
10 changed files with 346 additions and 16 deletions
|
|
@ -2,6 +2,10 @@
|
|||
|
||||
## [Unreleased]
|
||||
|
||||
### Added
|
||||
|
||||
- Added `ANTHROPIC_AUTH_TOKEN` bearer authentication for Anthropic-compatible gateways ([#5871](https://github.com/earendil-works/pi/issues/5871))
|
||||
|
||||
## [0.82.0] - 2026-07-24
|
||||
|
||||
### Breaking Changes
|
||||
|
|
|
|||
|
|
@ -26,6 +26,10 @@ if (typeof process !== "undefined" && (process.versions?.node || process.version
|
|||
import type { KnownProvider, ProviderEnv } from "./types.ts";
|
||||
import { getProviderEnvValue } from "./utils/provider-env.ts";
|
||||
|
||||
export const ANTHROPIC_AUTH_TOKEN_ENV = "ANTHROPIC_AUTH_TOKEN";
|
||||
export const ANTHROPIC_OAUTH_TOKEN_ENV = "ANTHROPIC_OAUTH_TOKEN";
|
||||
export const ANTHROPIC_API_KEY_ENV = "ANTHROPIC_API_KEY";
|
||||
|
||||
let cachedVertexAdcCredentialsExists: boolean | null = null;
|
||||
|
||||
function hasVertexAdcCredentials(env?: ProviderEnv): boolean {
|
||||
|
|
@ -66,9 +70,10 @@ function getApiKeyEnvVars(provider: string): readonly string[] | undefined {
|
|||
return ["COPILOT_GITHUB_TOKEN"];
|
||||
}
|
||||
|
||||
// ANTHROPIC_OAUTH_TOKEN takes precedence over ANTHROPIC_API_KEY
|
||||
// ANTHROPIC_AUTH_TOKEN participates in env discovery/status, but
|
||||
// getEnvApiKey() skips it because requests must pass it as Authorization: Bearer.
|
||||
if (provider === "anthropic") {
|
||||
return ["ANTHROPIC_OAUTH_TOKEN", "ANTHROPIC_API_KEY"];
|
||||
return [ANTHROPIC_AUTH_TOKEN_ENV, ANTHROPIC_OAUTH_TOKEN_ENV, ANTHROPIC_API_KEY_ENV];
|
||||
}
|
||||
|
||||
const envMap: Record<string, string> = {
|
||||
|
|
@ -139,7 +144,8 @@ export function getEnvApiKey(provider: string, env?: ProviderEnv): string | unde
|
|||
export function getEnvApiKey(provider: string, env?: ProviderEnv): string | undefined {
|
||||
const envKeys = findEnvKeys(provider, env);
|
||||
if (envKeys?.[0]) {
|
||||
return getProviderEnvValue(envKeys[0], env);
|
||||
const apiKeyEnv = provider === "anthropic" ? envKeys.find((key) => key !== ANTHROPIC_AUTH_TOKEN_ENV) : envKeys[0];
|
||||
if (apiKeyEnv) return getProviderEnvValue(apiKeyEnv, env);
|
||||
}
|
||||
|
||||
// Vertex AI supports either an explicit API key or Application Default Credentials.
|
||||
|
|
|
|||
|
|
@ -1,17 +1,47 @@
|
|||
import { anthropicMessagesApi } from "../api/anthropic-messages.lazy.ts";
|
||||
import { envApiKeyAuth, lazyOAuth } from "../auth/helpers.ts";
|
||||
import { lazyOAuth } from "../auth/helpers.ts";
|
||||
import { loadAnthropicOAuth } from "../auth/oauth/load.ts";
|
||||
import type { ApiKeyAuth } from "../auth/types.ts";
|
||||
import { ANTHROPIC_API_KEY_ENV, ANTHROPIC_AUTH_TOKEN_ENV, ANTHROPIC_OAUTH_TOKEN_ENV } from "../env-api-keys.ts";
|
||||
import { createProvider, type Provider } from "../models.ts";
|
||||
import { ANTHROPIC_MODELS } from "./anthropic.models.ts";
|
||||
|
||||
function anthropicApiKeyAuth(): ApiKeyAuth {
|
||||
return {
|
||||
name: "Anthropic API key",
|
||||
login: async (interaction) => ({
|
||||
type: "api_key",
|
||||
key: await interaction.prompt({ type: "secret", message: "Enter Anthropic API key" }),
|
||||
}),
|
||||
resolve: async ({ ctx, credential }) => {
|
||||
if (credential?.key) {
|
||||
return { auth: { apiKey: credential.key }, env: credential.env, source: "stored credential" };
|
||||
}
|
||||
|
||||
const authToken = await ctx.env(ANTHROPIC_AUTH_TOKEN_ENV);
|
||||
if (authToken) {
|
||||
return {
|
||||
auth: { headers: { Authorization: `Bearer ${authToken}` } },
|
||||
source: ANTHROPIC_AUTH_TOKEN_ENV,
|
||||
};
|
||||
}
|
||||
|
||||
for (const envVar of [ANTHROPIC_OAUTH_TOKEN_ENV, ANTHROPIC_API_KEY_ENV]) {
|
||||
const apiKey = await ctx.env(envVar);
|
||||
if (apiKey) return { auth: { apiKey }, source: envVar };
|
||||
}
|
||||
return undefined;
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
export function anthropicProvider(): Provider<"anthropic-messages"> {
|
||||
return createProvider({
|
||||
id: "anthropic",
|
||||
name: "Anthropic",
|
||||
baseUrl: "https://api.anthropic.com",
|
||||
auth: {
|
||||
// ANTHROPIC_OAUTH_TOKEN takes precedence over ANTHROPIC_API_KEY
|
||||
apiKey: envApiKeyAuth("Anthropic API key", ["ANTHROPIC_OAUTH_TOKEN", "ANTHROPIC_API_KEY"]),
|
||||
apiKey: anthropicApiKeyAuth(),
|
||||
oauth: lazyOAuth({ name: "Anthropic (Claude Pro/Max)", load: loadAnthropicOAuth }),
|
||||
},
|
||||
models: Object.values(ANTHROPIC_MODELS),
|
||||
|
|
|
|||
183
packages/ai/test/anthropic-auth-token.test.ts
Normal file
183
packages/ai/test/anthropic-auth-token.test.ts
Normal file
|
|
@ -0,0 +1,183 @@
|
|||
import { afterEach, describe, expect, it, vi } from "vitest";
|
||||
import { stream as streamAnthropic } from "../src/api/anthropic-messages.ts";
|
||||
import { ANTHROPIC_AUTH_TOKEN_ENV, ANTHROPIC_OAUTH_TOKEN_ENV } from "../src/env-api-keys.ts";
|
||||
import { createModels } from "../src/models.ts";
|
||||
import { anthropicProvider } from "../src/providers/anthropic.ts";
|
||||
import type { Context, Model } from "../src/types.ts";
|
||||
|
||||
const mockState = vi.hoisted(() => ({
|
||||
constructorOpts: undefined as Record<string, unknown> | undefined,
|
||||
createParams: undefined as Record<string, unknown> | undefined,
|
||||
}));
|
||||
|
||||
vi.mock("@anthropic-ai/sdk", () => {
|
||||
function createSseResponse(): Response {
|
||||
const body = [
|
||||
`event: message_start\ndata: ${JSON.stringify({
|
||||
type: "message_start",
|
||||
message: {
|
||||
id: "msg_test",
|
||||
usage: { input_tokens: 1, output_tokens: 0 },
|
||||
},
|
||||
})}\n`,
|
||||
`event: message_delta\ndata: ${JSON.stringify({
|
||||
type: "message_delta",
|
||||
delta: { stop_reason: "end_turn" },
|
||||
usage: { output_tokens: 1 },
|
||||
})}\n`,
|
||||
`event: message_stop\ndata: ${JSON.stringify({ type: "message_stop" })}\n`,
|
||||
].join("\n");
|
||||
|
||||
return new Response(body, {
|
||||
status: 200,
|
||||
headers: { "content-type": "text/event-stream" },
|
||||
});
|
||||
}
|
||||
|
||||
class FakeAnthropic {
|
||||
constructor(opts: Record<string, unknown>) {
|
||||
mockState.constructorOpts = opts;
|
||||
}
|
||||
messages = {
|
||||
create: (params: Record<string, unknown>) => {
|
||||
mockState.createParams = params;
|
||||
return {
|
||||
asResponse: async () => createSseResponse(),
|
||||
};
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
return { default: FakeAnthropic };
|
||||
});
|
||||
|
||||
const context: Context = {
|
||||
systemPrompt: "System prompt.",
|
||||
messages: [{ role: "user", content: "Hello", timestamp: Date.now() }],
|
||||
};
|
||||
|
||||
const anthropicModel: Model<"anthropic-messages"> = {
|
||||
id: "claude-test",
|
||||
name: "Claude Test",
|
||||
api: "anthropic-messages",
|
||||
provider: "anthropic",
|
||||
baseUrl: "https://api.anthropic.com",
|
||||
reasoning: false,
|
||||
input: ["text"],
|
||||
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
|
||||
contextWindow: 100000,
|
||||
maxTokens: 4096,
|
||||
};
|
||||
|
||||
afterEach(() => {
|
||||
mockState.constructorOpts = undefined;
|
||||
mockState.createParams = undefined;
|
||||
});
|
||||
|
||||
describe("Anthropic auth token env", () => {
|
||||
it("resolves ANTHROPIC_AUTH_TOKEN as a bearer Authorization header", async () => {
|
||||
const provider = anthropicProvider();
|
||||
const auth = await provider.auth.apiKey?.resolve({
|
||||
ctx: {
|
||||
env: async (name) =>
|
||||
({
|
||||
ANTHROPIC_AUTH_TOKEN: "auth-token",
|
||||
ANTHROPIC_OAUTH_TOKEN: "oauth-token",
|
||||
ANTHROPIC_API_KEY: "api-key",
|
||||
})[name],
|
||||
fileExists: async () => false,
|
||||
},
|
||||
});
|
||||
|
||||
expect(auth).toEqual({
|
||||
auth: { headers: { Authorization: "Bearer auth-token" } },
|
||||
source: ANTHROPIC_AUTH_TOKEN_ENV,
|
||||
});
|
||||
});
|
||||
|
||||
it("preserves ANTHROPIC_OAUTH_TOKEN as OAuth-shaped API auth", async () => {
|
||||
const provider = anthropicProvider();
|
||||
const auth = await provider.auth.apiKey?.resolve({
|
||||
ctx: {
|
||||
env: async (name) =>
|
||||
({
|
||||
ANTHROPIC_OAUTH_TOKEN: "oauth-token",
|
||||
ANTHROPIC_API_KEY: "api-key",
|
||||
})[name],
|
||||
fileExists: async () => false,
|
||||
},
|
||||
});
|
||||
|
||||
expect(auth).toEqual({
|
||||
auth: { apiKey: "oauth-token" },
|
||||
source: ANTHROPIC_OAUTH_TOKEN_ENV,
|
||||
});
|
||||
});
|
||||
|
||||
it("uses Authorization headers without OAuth-mode request shaping", async () => {
|
||||
const stream = streamAnthropic(anthropicModel, context, {
|
||||
headers: { Authorization: "Bearer gateway-token" },
|
||||
});
|
||||
await stream.result();
|
||||
|
||||
expect(mockState.constructorOpts?.apiKey).toBeNull();
|
||||
expect(mockState.constructorOpts?.authToken).toBeNull();
|
||||
const headers = mockState.constructorOpts?.defaultHeaders as Record<string, string | null>;
|
||||
expect(headers.Authorization).toBe("Bearer gateway-token");
|
||||
expect(headers["anthropic-beta"] ?? "").not.toContain("oauth-2025-04-20");
|
||||
expect(mockState.createParams?.system).toEqual([expect.objectContaining({ text: "System prompt." })]);
|
||||
});
|
||||
|
||||
it("threads authContext ANTHROPIC_AUTH_TOKEN through request headers", async () => {
|
||||
const models = createModels({
|
||||
authContext: {
|
||||
env: async (name) => (name === "ANTHROPIC_AUTH_TOKEN" ? "ctx-token" : undefined),
|
||||
fileExists: async () => false,
|
||||
},
|
||||
});
|
||||
models.setProvider(anthropicProvider());
|
||||
|
||||
await models.streamSimple(anthropicModel, context).result();
|
||||
|
||||
expect(mockState.constructorOpts?.apiKey).toBeNull();
|
||||
expect(mockState.constructorOpts?.authToken).toBeNull();
|
||||
const headers = mockState.constructorOpts?.defaultHeaders as Record<string, string>;
|
||||
expect(headers.Authorization).toBe("Bearer ctx-token");
|
||||
expect(headers["anthropic-beta"] ?? "").not.toContain("oauth-2025-04-20");
|
||||
expect(mockState.createParams?.system).toEqual([expect.objectContaining({ text: "System prompt." })]);
|
||||
});
|
||||
|
||||
it("preserves OAuth request shaping for ANTHROPIC_OAUTH_TOKEN", async () => {
|
||||
const models = createModels({
|
||||
authContext: {
|
||||
env: async (name) => (name === "ANTHROPIC_OAUTH_TOKEN" ? "sk-ant-oat-test" : undefined),
|
||||
fileExists: async () => false,
|
||||
},
|
||||
});
|
||||
models.setProvider(anthropicProvider());
|
||||
|
||||
await models.streamSimple(anthropicModel, context).result();
|
||||
|
||||
expect(mockState.constructorOpts?.apiKey).toBeNull();
|
||||
expect(mockState.constructorOpts?.authToken).toBe("sk-ant-oat-test");
|
||||
const headers = mockState.constructorOpts?.defaultHeaders as Record<string, string>;
|
||||
expect(headers["anthropic-beta"]).toContain("oauth-2025-04-20");
|
||||
});
|
||||
|
||||
it("lets explicit request headers override ANTHROPIC_AUTH_TOKEN", async () => {
|
||||
const models = createModels({
|
||||
authContext: {
|
||||
env: async (name) => (name === "ANTHROPIC_AUTH_TOKEN" ? "ctx-token" : undefined),
|
||||
fileExists: async () => false,
|
||||
},
|
||||
});
|
||||
models.setProvider(anthropicProvider());
|
||||
|
||||
await models
|
||||
.streamSimple(anthropicModel, context, { headers: { Authorization: "Bearer explicit-token" } })
|
||||
.result();
|
||||
|
||||
const headers = mockState.constructorOpts?.defaultHeaders as Record<string, string>;
|
||||
expect(headers.Authorization).toBe("Bearer explicit-token");
|
||||
});
|
||||
});
|
||||
|
|
@ -5,6 +5,9 @@ const originalCopilotGitHubToken = process.env.COPILOT_GITHUB_TOKEN;
|
|||
const originalGhToken = process.env.GH_TOKEN;
|
||||
const originalGitHubToken = process.env.GITHUB_TOKEN;
|
||||
const originalZaiCodingCnApiKey = process.env.ZAI_CODING_CN_API_KEY;
|
||||
const originalAnthropicAuthToken = process.env.ANTHROPIC_AUTH_TOKEN;
|
||||
const originalAnthropicOauthToken = process.env.ANTHROPIC_OAUTH_TOKEN;
|
||||
const originalAnthropicApiKey = process.env.ANTHROPIC_API_KEY;
|
||||
|
||||
afterEach(() => {
|
||||
if (originalCopilotGitHubToken === undefined) {
|
||||
|
|
@ -30,6 +33,24 @@ afterEach(() => {
|
|||
} else {
|
||||
process.env.ZAI_CODING_CN_API_KEY = originalZaiCodingCnApiKey;
|
||||
}
|
||||
|
||||
if (originalAnthropicAuthToken === undefined) {
|
||||
delete process.env.ANTHROPIC_AUTH_TOKEN;
|
||||
} else {
|
||||
process.env.ANTHROPIC_AUTH_TOKEN = originalAnthropicAuthToken;
|
||||
}
|
||||
|
||||
if (originalAnthropicOauthToken === undefined) {
|
||||
delete process.env.ANTHROPIC_OAUTH_TOKEN;
|
||||
} else {
|
||||
process.env.ANTHROPIC_OAUTH_TOKEN = originalAnthropicOauthToken;
|
||||
}
|
||||
|
||||
if (originalAnthropicApiKey === undefined) {
|
||||
delete process.env.ANTHROPIC_API_KEY;
|
||||
} else {
|
||||
process.env.ANTHROPIC_API_KEY = originalAnthropicApiKey;
|
||||
}
|
||||
});
|
||||
|
||||
describe("environment API keys", () => {
|
||||
|
|
@ -57,4 +78,39 @@ describe("environment API keys", () => {
|
|||
expect(findEnvKeys("zai-coding-cn")).toEqual(["ZAI_CODING_CN_API_KEY"]);
|
||||
expect(getEnvApiKey("zai-coding-cn")).toBe("zai-coding-cn-token");
|
||||
});
|
||||
|
||||
it("reports ANTHROPIC_AUTH_TOKEN but preserves OAuth token API key lookup", () => {
|
||||
process.env.ANTHROPIC_AUTH_TOKEN = "auth-token";
|
||||
process.env.ANTHROPIC_OAUTH_TOKEN = "oauth-token";
|
||||
process.env.ANTHROPIC_API_KEY = "api-key";
|
||||
|
||||
expect(findEnvKeys("anthropic")).toEqual(["ANTHROPIC_AUTH_TOKEN", "ANTHROPIC_OAUTH_TOKEN", "ANTHROPIC_API_KEY"]);
|
||||
expect(getEnvApiKey("anthropic")).toBe("oauth-token");
|
||||
});
|
||||
|
||||
it("does not return ANTHROPIC_AUTH_TOKEN as an API key", () => {
|
||||
process.env.ANTHROPIC_AUTH_TOKEN = "auth-token";
|
||||
delete process.env.ANTHROPIC_OAUTH_TOKEN;
|
||||
delete process.env.ANTHROPIC_API_KEY;
|
||||
|
||||
expect(findEnvKeys("anthropic")).toEqual(["ANTHROPIC_AUTH_TOKEN"]);
|
||||
expect(getEnvApiKey("anthropic")).toBeUndefined();
|
||||
});
|
||||
|
||||
it("preserves ANTHROPIC_OAUTH_TOKEN as an API key", () => {
|
||||
delete process.env.ANTHROPIC_AUTH_TOKEN;
|
||||
process.env.ANTHROPIC_OAUTH_TOKEN = "oauth-token";
|
||||
delete process.env.ANTHROPIC_API_KEY;
|
||||
|
||||
expect(findEnvKeys("anthropic")).toEqual(["ANTHROPIC_OAUTH_TOKEN"]);
|
||||
expect(getEnvApiKey("anthropic")).toBe("oauth-token");
|
||||
});
|
||||
|
||||
it("falls back to ANTHROPIC_API_KEY for API key lookup", () => {
|
||||
delete process.env.ANTHROPIC_AUTH_TOKEN;
|
||||
delete process.env.ANTHROPIC_OAUTH_TOKEN;
|
||||
process.env.ANTHROPIC_API_KEY = "api-key";
|
||||
|
||||
expect(getEnvApiKey("anthropic")).toBe("api-key");
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -79,14 +79,29 @@ describe("builtin providers", () => {
|
|||
}
|
||||
});
|
||||
|
||||
it("resolves anthropic auth from env with OAuth token precedence", async () => {
|
||||
it("resolves Anthropic bearer auth from env with auth token precedence", async () => {
|
||||
const models = createModels({
|
||||
authContext: fakeAuthContext({
|
||||
ANTHROPIC_AUTH_TOKEN: "auth-token",
|
||||
ANTHROPIC_OAUTH_TOKEN: "oauth-token",
|
||||
ANTHROPIC_API_KEY: "api-key",
|
||||
}),
|
||||
});
|
||||
models.setProvider(anthropicProvider());
|
||||
|
||||
expect(await models.getAuth("anthropic")).toEqual({
|
||||
auth: { headers: { Authorization: "Bearer auth-token" } },
|
||||
source: "ANTHROPIC_AUTH_TOKEN",
|
||||
});
|
||||
});
|
||||
|
||||
it("preserves Anthropic OAuth token precedence over the API key", async () => {
|
||||
const models = createModels({
|
||||
authContext: fakeAuthContext({ ANTHROPIC_API_KEY: "key", ANTHROPIC_OAUTH_TOKEN: "oauth-token" }),
|
||||
});
|
||||
models.setProvider(anthropicProvider());
|
||||
const model = models.getModel("anthropic", "claude-haiku-4-5")!;
|
||||
|
||||
const result = await models.getAuth(model.provider);
|
||||
const result = await models.getAuth("anthropic");
|
||||
expect(result?.auth.apiKey).toBe("oauth-token");
|
||||
expect(result?.source).toBe("ANTHROPIC_OAUTH_TOKEN");
|
||||
});
|
||||
|
|
|
|||
|
|
@ -2,6 +2,10 @@
|
|||
|
||||
## [Unreleased]
|
||||
|
||||
### Fixed
|
||||
|
||||
- Fixed compaction and branch summaries for providers whose authentication resolves entirely to request headers ([#5871](https://github.com/earendil-works/pi/issues/5871))
|
||||
|
||||
## [0.82.0] - 2026-07-24
|
||||
|
||||
### New Features
|
||||
|
|
|
|||
|
|
@ -333,6 +333,7 @@ ${chalk.bold("Examples:")}
|
|||
${APP_NAME} --export session.jsonl output.html
|
||||
|
||||
${chalk.bold("Environment Variables:")}
|
||||
ANTHROPIC_AUTH_TOKEN - Anthropic bearer auth token
|
||||
ANTHROPIC_API_KEY - Anthropic Claude API key
|
||||
ANTHROPIC_OAUTH_TOKEN - Anthropic OAuth token (alternative to API key)
|
||||
ANT_LING_API_KEY - Ant Ling API key
|
||||
|
|
|
|||
|
|
@ -405,7 +405,7 @@ export class AgentSession {
|
|||
}
|
||||
|
||||
private async _getRequiredRequestAuth(model: Model<any>): Promise<{
|
||||
apiKey: string;
|
||||
apiKey?: string;
|
||||
headers?: Record<string, string>;
|
||||
env?: Record<string, string>;
|
||||
}> {
|
||||
|
|
@ -419,7 +419,7 @@ export class AgentSession {
|
|||
}
|
||||
throw error;
|
||||
}
|
||||
if (result?.auth.apiKey) {
|
||||
if (result && (result.auth.apiKey || result.auth.headers)) {
|
||||
return {
|
||||
apiKey: result.auth.apiKey,
|
||||
headers: withoutDeletedHeaders(result.auth.headers),
|
||||
|
|
@ -2057,11 +2057,7 @@ export class AgentSession {
|
|||
let headers: Record<string, string> | undefined;
|
||||
let env: Record<string, string> | undefined;
|
||||
if (this.agent.streamFunction === streamSimple) {
|
||||
const authResult = await this._modelRuntime.getAuth(this.model);
|
||||
if (!authResult?.auth.apiKey) return false;
|
||||
apiKey = authResult.auth.apiKey;
|
||||
headers = withoutDeletedHeaders(authResult.auth.headers);
|
||||
env = authResult.env;
|
||||
({ apiKey, headers, env } = await this._getRequiredRequestAuth(this.model));
|
||||
} else {
|
||||
({ apiKey, headers, env } = await this._getSummarizationRequestAuth(this.model));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -175,6 +175,41 @@ describe("AgentSession compaction characterization", () => {
|
|||
expect(getStreamCallCount()).toBe(1);
|
||||
});
|
||||
|
||||
it("manually compacts with provider-resolved bearer auth", async () => {
|
||||
const harness = await createHarness({ withConfiguredAuth: false });
|
||||
harnesses.push(harness);
|
||||
const model = harness.getModel();
|
||||
harness.session.modelRuntime.registerNativeProvider({
|
||||
id: model.provider,
|
||||
name: "Faux bearer provider",
|
||||
auth: {
|
||||
apiKey: {
|
||||
name: "Faux bearer token",
|
||||
resolve: async () => ({
|
||||
auth: { headers: { Authorization: "Bearer ambient-token" } },
|
||||
source: "ambient bearer token",
|
||||
}),
|
||||
},
|
||||
},
|
||||
getModels: () => harness.models,
|
||||
stream: () => createAssistantMessageEventStream(),
|
||||
streamSimple: () => createAssistantMessageEventStream(),
|
||||
});
|
||||
seedCompactableSession(harness);
|
||||
harness.setResponses([
|
||||
(_context, options) => {
|
||||
expect(options?.apiKey).toBeUndefined();
|
||||
expect(options?.headers).toEqual({ Authorization: "Bearer ambient-token" });
|
||||
return fauxAssistantMessage("summary with bearer auth");
|
||||
},
|
||||
]);
|
||||
|
||||
const result = await harness.session.compact();
|
||||
|
||||
expect(result.summary).toContain("summary with bearer auth");
|
||||
expect(harness.faux.state.callCount).toBe(1);
|
||||
});
|
||||
|
||||
it("persists usage from pi-generated manual compaction", async () => {
|
||||
const harness = await createHarness({ withConfiguredAuth: false });
|
||||
harnesses.push(harness);
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue