Skip to content

Commit a9feb29

Browse files
MrFlounderclaude
andcommitted
feat(promptfoo): add crab pf serve Slack polling daemon
Adds a local daemon that polls for Slack DMs and runs the promptfoo target discovery agent on behalf of team members. Non-technical users can DM the Crab bot with "pf: <target spec>" and receive generated promptfooconfig.yaml files back in-thread. - New slack.ts: Slack Web API helpers using native fetch (zero deps) - New serve.ts: daemon loop with setup flow, DM polling, and job mgmt - Updated cli.ts: routes `serve` subcommand, adds --reasoning flag - Updated providers.ts: reasoningEffort support for GPT-5/o-series Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent b013517 commit a9feb29

5 files changed

Lines changed: 887 additions & 7 deletions

File tree

plugins/promptfoo/package-lock.json

Lines changed: 3 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

plugins/promptfoo/src/agent/providers.ts

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ export interface ChatOptions {
2222
tools: unknown[];
2323
maxTokens?: number;
2424
temperature?: number;
25+
reasoningEffort?: 'low' | 'medium' | 'high';
2526
}
2627

2728
export interface ChatResponse {
@@ -41,18 +42,21 @@ export class OpenAIProvider implements LLMProvider {
4142
private apiKey: string;
4243
private model: string;
4344
private baseUrl: string;
45+
reasoningEffort?: string;
4446

45-
constructor(options: { apiKey?: string; model?: string; baseUrl?: string }) {
47+
constructor(options: { apiKey?: string; model?: string; baseUrl?: string; reasoningEffort?: string }) {
4648
this.apiKey = options.apiKey || process.env.OPENAI_API_KEY || '';
4749
this.model = options.model || 'gpt-4o';
4850
this.baseUrl = options.baseUrl || 'https://api.openai.com/v1';
51+
this.reasoningEffort = options.reasoningEffort;
4952

5053
if (!this.apiKey) {
5154
throw new Error('OpenAI API key required. Set OPENAI_API_KEY env var.');
5255
}
5356
}
5457

5558
async chat(options: ChatOptions): Promise<ChatResponse> {
59+
const isReasoning = this.model.startsWith('gpt-5') || this.model.startsWith('o1') || this.model.startsWith('o3');
5660
const response = await fetch(`${this.baseUrl}/chat/completions`, {
5761
method: 'POST',
5862
headers: {
@@ -63,8 +67,13 @@ export class OpenAIProvider implements LLMProvider {
6367
model: this.model,
6468
messages: options.messages.map((m) => this.toOpenAIMessage(m)),
6569
tools: options.tools,
66-
max_tokens: options.maxTokens || 4096,
67-
temperature: options.temperature ?? 0.7,
70+
...(isReasoning
71+
? { max_completion_tokens: options.maxTokens || 4096 }
72+
: { max_tokens: options.maxTokens || 4096 }),
73+
...(isReasoning ? {} : { temperature: options.temperature ?? 0.7 }),
74+
...(options.reasoningEffort || this.reasoningEffort
75+
? { reasoning_effort: options.reasoningEffort || this.reasoningEffort }
76+
: {}),
6877
}),
6978
});
7079

@@ -253,12 +262,12 @@ export class AnthropicProvider implements LLMProvider {
253262
/**
254263
* Create provider from string identifier
255264
*/
256-
export function createProvider(provider: string): LLMProvider {
265+
export function createProvider(provider: string, options?: { reasoningEffort?: string }): LLMProvider {
257266
const [type, model] = provider.split(':');
258267

259268
switch (type) {
260269
case 'openai':
261-
return new OpenAIProvider({ model: model || 'gpt-4o' });
270+
return new OpenAIProvider({ model: model || 'gpt-4o', reasoningEffort: options?.reasoningEffort });
262271
case 'anthropic':
263272
return new AnthropicProvider({ model: model || 'claude-sonnet-4-20250514' });
264273
default:

plugins/promptfoo/src/cli.ts

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,18 +15,26 @@ import * as path from 'node:path';
1515
import { parseArtifact, detectFormat } from './parsers/index.js';
1616
import { runDiscoveryAgent } from './agent/loop.js';
1717
import { createProvider } from './agent/providers.js';
18+
import { runServe } from './serve.js';
1819

1920
const args = process.argv.slice(2);
2021

2122
async function main() {
2223
try {
24+
// Handle 'serve' subcommand
25+
if (args[0] === 'serve') {
26+
await runServe(args.slice(1));
27+
return;
28+
}
29+
2330
// Parse arguments
2431
const filePath = getArg('--file') || getArg('-f');
2532
const urlArg = getArg('--url');
2633
const providerStr = getArg('--provider') || process.env.DISCOVERY_PROVIDER || 'openai:gpt-4o';
2734
const outputDir = getArg('--output') || getArg('-o') || '.';
2835
const verbose = args.includes('--verbose') || args.includes('-v');
2936
const maxTurns = parseInt(getArg('--max-turns') || '30', 10);
37+
const reasoningEffort = getArg('--reasoning');
3038

3139
let context: string;
3240

@@ -52,7 +60,7 @@ async function main() {
5260
// Detect format
5361
const format = detectFormat(context);
5462
console.log(`Detected format: ${format === 'unknown' ? 'text/description' : format}`);
55-
console.log(`Provider: ${providerStr}`);
63+
console.log(`Provider: ${providerStr}${reasoningEffort ? ` (reasoning: ${reasoningEffort})` : ''}`);
5664
console.log(`Output: ${outputDir}`);
5765
console.log('');
5866

@@ -62,7 +70,7 @@ async function main() {
6270
}
6371

6472
// Create provider
65-
const provider = createProvider(providerStr);
73+
const provider = createProvider(providerStr, reasoningEffort ? { reasoningEffort } : undefined);
6674

6775
// Run discovery agent
6876
console.log('Starting target discovery agent...');
@@ -134,6 +142,7 @@ Options:
134142
--output, -o <dir> Output directory (default: current dir)
135143
--provider <provider> LLM provider (default: openai:gpt-4o)
136144
--max-turns <n> Max agent turns (default: 30)
145+
--reasoning <effort> Reasoning effort for GPT-5/o-series (low, medium, high)
137146
--verbose, -v Show detailed output
138147
139148
Supported input formats:
@@ -158,6 +167,11 @@ Examples:
158167
# Using Anthropic
159168
crab pf --file target.txt --provider anthropic:claude-sonnet-4-20250514
160169
170+
Subcommands:
171+
serve Run Slack polling daemon
172+
serve --setup Configure Slack username
173+
serve --help Show serve help
174+
161175
Output:
162176
The agent will create:
163177
- promptfooconfig.yaml (promptfoo configuration)

0 commit comments

Comments
 (0)