Skip to content
Open
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
9 changes: 8 additions & 1 deletion src/lib/env.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,14 @@ export const envSchema: z.ZodType<Env, unknown> = z.object({
OPENROUTER_APP_TITLE: z.string().optional(),
OPENROUTER_APP_CATEGORIES: z.string().optional(),

OPENROUTER_DEBUG: z.coerce.boolean().optional(),
OPENROUTER_DEBUG: z.preprocess(

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[suggestion] src/lib/env.ts carries a Code generated by Speakeasy — DO NOT EDIT header — verify this change survives regeneration.

Details

Why: Speakeasy regenerates this file from the OpenAPI spec. With persistentEdits: enabled: "true" in .speakeasy/gen.yaml, Speakeasy will attempt to re-apply manual edits after regeneration, but this is not guaranteed — especially for logic changes inside schema fields. If a Speakeasy upgrade or spec bump triggers full file regeneration, the preprocess guard and tests may silently diverge from the schema.

Options:

  1. Run speakeasy run locally and confirm the preprocess block survives in the regenerated output.
  2. If persistentEdits is unreliable for this pattern, add a Speakeasy overlay (similar to .speakeasy/overlays/boolean-query-params.overlay.yaml) to make this the generated default.
Prompt for agents
In `src/lib/env.ts` at line 38, the manual `z.preprocess` block for `OPENROUTER_DEBUG` lives inside a Speakeasy-generated file (`DO NOT EDIT` header). Verify that `.speakeasy/gen.yaml`'s `persistentEdits: enabled: "true"` actually preserves this block by running `speakeasy run` locally and confirming the preprocess logic is still present in the regenerated output. If not preserved, either add a `.speakeasy/overlays/` overlay to codify this schema change, or document the manual steps required after each regeneration.

Reviewed at eed9ad8

(v) => {
if(typeof v === "string") {
return !["false", "0", "off", "no", ""].includes(v.toLowerCase().trim())
}
return v;
},
z.coerce.boolean().optional()),
});

/**
Expand Down
120 changes: 120 additions & 0 deletions tests/unit/env-debug.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
import { describe, it, expect, beforeEach } from 'vitest';
import { envSchema, resetEnv } from '../../src/lib/env.js';

describe('OPENROUTER_DEBUG env var parsing', () => {
beforeEach(() => {
resetEnv();
});

describe('explicit falsy values should disable debug logging', () => {
it('should parse OPENROUTER_DEBUG=false as false', () => {
const result = envSchema.parse({ OPENROUTER_DEBUG: 'false' });
expect(result.OPENROUTER_DEBUG).toBe(false);
});

it('should parse OPENROUTER_DEBUG=0 as false', () => {
const result = envSchema.parse({ OPENROUTER_DEBUG: '0' });
expect(result.OPENROUTER_DEBUG).toBe(false);
});

it('should parse OPENROUTER_DEBUG=off as false', () => {
const result = envSchema.parse({ OPENROUTER_DEBUG: 'off' });
expect(result.OPENROUTER_DEBUG).toBe(false);
});

it('should parse OPENROUTER_DEBUG=no as false', () => {
const result = envSchema.parse({ OPENROUTER_DEBUG: 'no' });
expect(result.OPENROUTER_DEBUG).toBe(false);
});

it('should parse OPENROUTER_DEBUG="" as false', () => {
const result = envSchema.parse({ OPENROUTER_DEBUG: '' });
expect(result.OPENROUTER_DEBUG).toBe(false);
});
});

describe('case-insensitive handling', () => {
it('should parse OPENROUTER_DEBUG=FALSE as false', () => {
const result = envSchema.parse({ OPENROUTER_DEBUG: 'FALSE' });
expect(result.OPENROUTER_DEBUG).toBe(false);
});

it('should parse OPENROUTER_DEBUG=False as false', () => {
const result = envSchema.parse({ OPENROUTER_DEBUG: 'False' });
expect(result.OPENROUTER_DEBUG).toBe(false);
});

it('should parse OPENROUTER_DEBUG=OFF as false', () => {
const result = envSchema.parse({ OPENROUTER_DEBUG: 'OFF' });
expect(result.OPENROUTER_DEBUG).toBe(false);
});
});

describe('explicit truthy values should enable debug logging', () => {
it('should parse OPENROUTER_DEBUG=true as true', () => {
const result = envSchema.parse({ OPENROUTER_DEBUG: 'true' });
expect(result.OPENROUTER_DEBUG).toBe(true);
});

it('should parse OPENROUTER_DEBUG=1 as true', () => {
const result = envSchema.parse({ OPENROUTER_DEBUG: '1' });
expect(result.OPENROUTER_DEBUG).toBe(true);
});

it('should parse OPENROUTER_DEBUG=on as true', () => {
const result = envSchema.parse({ OPENROUTER_DEBUG: 'on' });
expect(result.OPENROUTER_DEBUG).toBe(true);
});

it('should parse OPENROUTER_DEBUG=yes as true', () => {
const result = envSchema.parse({ OPENROUTER_DEBUG: 'yes' });
expect(result.OPENROUTER_DEBUG).toBe(true);
});
});

describe('actual boolean values passed programmatically', () => {
it('should accept boolean true', () => {
const result = envSchema.parse({ OPENROUTER_DEBUG: true });
expect(result.OPENROUTER_DEBUG).toBe(true);
});

it('should accept boolean false', () => {
const result = envSchema.parse({ OPENROUTER_DEBUG: false });
expect(result.OPENROUTER_DEBUG).toBe(false);
});
});

describe('undefined/unset behavior', () => {
it('should be undefined when not set', () => {
const result = envSchema.parse({});
expect(result.OPENROUTER_DEBUG).toBeUndefined();
});

it('should be undefined when explicitly undefined', () => {
const result = envSchema.parse({ OPENROUTER_DEBUG: undefined });
expect(result.OPENROUTER_DEBUG).toBeUndefined();
});
});

describe('regression: any non-empty string without the fix would be truthy', () => {
it('should NOT parse OPENROUTER_DEBUG= as true (empty string edge case)', () => {
const result = envSchema.parse({ OPENROUTER_DEBUG: '' });
expect(result.OPENROUTER_DEBUG).toBe(false);
});

it('should parse OPENROUTER_DEBUG=disabled as true (arbitrary non-empty value)', () => {
const result = envSchema.parse({ OPENROUTER_DEBUG: 'disabled' });
expect(result.OPENROUTER_DEBUG).toBe(true);
});
});

describe('resetEnv clears cache between tests', () => {
it('should return fresh value after reset', () => {
const result1 = envSchema.parse({ OPENROUTER_DEBUG: 'true' });
expect(result1.OPENROUTER_DEBUG).toBe(true);
resetEnv();
const result2 = envSchema.parse({ OPENROUTER_DEBUG: 'false' });
expect(result2.OPENROUTER_DEBUG).toBe(false);
});
});
});