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
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,5 @@ build/
.env.*
.DS_Store
.claude/worktrees/
.waza-results/
.waza-cache/
31 changes: 31 additions & 0 deletions .waza.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
# yaml-language-server: $schema=https://raw.githubusercontent.com/microsoft/waza/main/schemas/config.schema.json

paths:
skills: skills
evals: evals
results: .waza-results
defaults:
engine: copilot-sdk
model: claude-sonnet-4.6
timeout: 300
parallel: false
workers: 4
verbose: false
sessionLog: false
cache:
enabled: false
dir: .waza-cache
server:
port: 3000
resultsDir: results/
dev:
model: claude-sonnet-4-20250514
target: medium-high
maxIterations: 5
tokens:
warningThreshold: 500
fallbackLimit: 1000
graders:
programTimeout: 30
storage:
containerName: waza-results
55 changes: 55 additions & 0 deletions evals/_hooks/prepare-skill.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
#!/usr/bin/env bun

import { spawnSync } from "node:child_process";
import { existsSync } from "node:fs";
import { homedir } from "node:os";
import { dirname, join, resolve } from "node:path";
import { fileURLToPath } from "node:url";

// NOTE: Waza runs hooks without a shell, so one TypeScript command handles setup.

function main(): void {
const skill = process.argv[2];
if (!skill) {
console.error("usage: prepare-skill.ts <skill-name>");
process.exit(2);
}

const here = dirname(fileURLToPath(import.meta.url));
const repoRoot = resolve(here, "..", "..");
const srcDir = join(repoRoot, "skills", skill);
if (!existsSync(srcDir)) {
console.error(`❌ skill source not found: ${srcDir}`);
process.exit(1);
}

const destSkillsRoot = join(homedir(), ".agents", "skills");
const destDir = join(destSkillsRoot, skill);

const mk = spawnSync("mkdir", ["-p", destSkillsRoot], { stdio: "inherit" });
if (mk.status !== 0) {
process.exit(mk.status ?? 1);
}

const sync = spawnSync("rsync", ["-a", "--delete", `${srcDir}/`, `${destDir}/`], {
stdio: "inherit",
});
if (sync.status !== 0) {
process.exit(sync.status ?? 1);
}

const scriptsPkg = join(destDir, "scripts", "package.json");
if (existsSync(scriptsPkg)) {
const install = spawnSync("npm", ["install", "--silent"], {
cwd: join(destDir, "scripts"),
stdio: "inherit",
});
if (install.status !== 0) {
process.exit(install.status ?? 1);
}
}

console.error(`✓ prepared skill "${skill}" → ${destDir}`);
}

main();
32 changes: 32 additions & 0 deletions evals/openrouter-images/eval.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
name: openrouter-images-eval
description: |
Evaluates routing among image discovery, generation, and editing; indirect
triggers and anti-triggers; and migration to POST /api/v1/images with
data[].b64_json responses.
skill: openrouter-images
version: "1.0"
config:
trials_per_task: 1
timeout_seconds: 300
parallel: false
executor: copilot-sdk
model: claude-opus-4.7
metrics:
- name: task_completion
weight: 1.0
threshold: 0.8
description: Did the agent choose the correct workflow and dedicated API shape?

hooks:
before_run:
- command: "bun evals/_hooks/prepare-skill.ts openrouter-images"

graders:
- type: code
name: has_output
config:
assertions:
- "len(output) > 50"

tasks:
- "tasks/*.yaml"
30 changes: 30 additions & 0 deletions evals/openrouter-images/tasks/01-generate-basic.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
id: generate-basic-001
name: Generate Basic Image
description: |
A new-image request should route to generate.ts on the dedicated Images API,
not edit.ts or a raw legacy endpoint.
tags:
- happy-path
- generate

inputs:
prompt: |
Generate an image of a red panda wearing sunglasses. Do not execute the
request because my API key is unavailable; show the exact bundled-script
command you would run.

graders:
- type: code
name: uses_generate_script
config:
language: python
assertions:
- '"generate.ts" in output'
- '"edit.ts" not in output'
- '"red panda" in output.lower() and "sunglasses" in output.lower()'
- '"/api/v1/chat/completions" not in output and "/api/v1/responses" not in output'


expected:
outcomes:
- type: task_completed
30 changes: 30 additions & 0 deletions evals/openrouter-images/tasks/02-generate-with-aspect-ratio.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
id: generate-aspect-ratio-001
name: Generate With Aspect Ratio
description: |
A specified landscape ratio should be passed through the bundled generate
script rather than only mentioned in prose.
tags:
- happy-path
- generate
- aspect-ratio

inputs:
prompt: |
Make a wide landscape image of a futuristic city at night, 16:9. Do not
execute it; show the exact bundled-script command.

graders:
- type: code
name: aspect_ratio_flag
config:
language: python
assertions:
- '"generate.ts" in output'
- '"--aspect-ratio" in output'
- '"16:9" in output'
- '"city" in output.lower() and "night" in output.lower()'


expected:
outcomes:
- type: task_completed
38 changes: 38 additions & 0 deletions evals/openrouter-images/tasks/03-edit-image.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
id: edit-image-001
name: Edit Existing Image
description: |
User wants to modify an existing image. The agent should use edit.ts,
pass the source path first, and avoid generate.ts and raw API calls.
tags:
- happy-path
- edit

inputs:
prompt: |
I have a file called photo.png. Edit it so the sky is purple. Do not
execute the request because my API key is unavailable; show the exact
bundled-script command you would run.

graders:
- type: code
name: uses_only_edit_script
config:
language: python
assertions:
- '"edit.ts" in output'
- '"generate.ts" not in output'
- '"/api/v1/chat/completions" not in output and "/api/v1/responses" not in output'

- type: code
name: passes_source_then_prompt
config:
language: python
assertions:
- '"photo.png" in output'
- '"purple" in output.lower() and "sky" in output.lower()'
- 'output.find("photo.png") < output.lower().find("purple")'


expected:
outcomes:
- type: task_completed
28 changes: 28 additions & 0 deletions evals/openrouter-images/tasks/04-anti-trigger-image-theory.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
id: anti-trigger-image-theory-001
name: Anti-Trigger - Image Generation Theory
description: |
A conceptual question should receive an explanation without invoking or
proposing image-generation scripts.
tags:
- anti-trigger
- negative-test

inputs:
prompt: |
How do diffusion models generate images from text? Explain the basic idea.
I am curious about the technique, not looking to generate anything.

graders:
- type: code
name: no_image_scripts
config:
language: python
assertions:
- '"generate.ts" not in output'
- '"edit.ts" not in output'
- '"discover.ts" not in output'


expected:
outcomes:
- type: task_completed
30 changes: 30 additions & 0 deletions evals/openrouter-images/tasks/05-indirect-trigger-blog-hero.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
id: indirect-trigger-blog-hero-001
name: Indirect Trigger - Blog Hero Image
description: |
A request for blog artwork should route to image generation even without the
phrase "generate an image", with an appropriate wide ratio.
tags:
- happy-path
- indirect-trigger

inputs:
prompt: |
I am writing a blog post about remote work and need a hero image for the
top of the page: a quiet home office with good natural light. Do not execute
it; show the exact bundled-script command you would run.

graders:
- type: code
name: hero_generation_command
config:
language: python
assertions:
- '"generate.ts" in output'
- '"home office" in output.lower() or "remote work" in output.lower()'
- '"--aspect-ratio" in output'
- '("16:9" in output or "21:9" in output or "3:1" in output or "2:1" in output)'


expected:
outcomes:
- type: task_completed
29 changes: 29 additions & 0 deletions evals/openrouter-images/tasks/06-discover-model-capabilities.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
id: discover-model-capabilities-001
name: Discover Model Capabilities
description: |
A model capability question should use discover.ts with the model ID before
recommending unsupported generation options.
tags:
- discovery
- model-selection

inputs:
prompt: |
Before I generate anything, how would you check which parameters and
providers google/gemini-3.1-flash-image-preview supports? Show the exact
bundled command, but do not execute it.

graders:
- type: code
name: uses_discovery_script
config:
language: python
assertions:
- '"discover.ts" in output'
- '"google/gemini-3.1-flash-image-preview" in output'
- '"generate.ts" not in output and "edit.ts" not in output'


expected:
outcomes:
- type: task_completed
32 changes: 32 additions & 0 deletions evals/openrouter-images/tasks/07-dedicated-api-code.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
id: dedicated-images-api-code-001
name: Dedicated Images API Code
description: |
A developer asking for direct API code should be taught the new dedicated
endpoint and response shape rather than legacy chat or responses formats.
tags:
- api
- migration

inputs:
prompt: |
Give me a minimal TypeScript fetch example for generating an image through
OpenRouter's new dedicated images endpoint. Include how to read the returned
base64 image, but do not send a real request.

graders:
- type: code
name: dedicated_endpoint_shape
config:
language: python
assertions:
- '"/api/v1/images" in output'
- '"POST" in output.upper()'
- '"b64_json" in output'
- '"data" in output'
- '"/api/v1/chat/completions" not in output'
- '"choices" not in output or "not" in output.lower()'


expected:
outcomes:
- type: task_completed
2 changes: 1 addition & 1 deletion skills/openrouter-images/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ gh skill install OpenRouterTeam/skills openrouter-images

Works with Claude Code, Cursor, Codex, OpenCode, Gemini CLI, Windsurf, and [many more agents](https://cli.github.com/manual/gh_skill_install). Add `--scope user` to install across every project for your current agent, or `--agent claude-code` to target a specific agent.

For other install methods (Claude Code plugin marketplace, Cursor Rules, etc.) see the [root README](../../README.md#installing).
The repository root README documents additional install methods for Claude Code, Cursor, and other agents.

## Prerequisites

Expand Down
Loading