@wix/pathgrade 1.0.10 → 1.0.12

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -206,7 +206,28 @@ export async function getToolEvents(events, actionFilter) {
206
206
  const filtered = actionFilter
207
207
  ? events.filter((e) => e.action.includes(actionFilter))
208
208
  : events;
209
- return JSON.stringify(filtered);
209
+ const serialized = JSON.stringify(filtered);
210
+ const maxChars = 200 * 1024;
211
+ if (serialized.length <= maxChars)
212
+ return serialized;
213
+ const included = [];
214
+ for (const event of filtered) {
215
+ const candidate = JSON.stringify({
216
+ events: [...included, event],
217
+ truncated: true,
218
+ totalEvents: filtered.length,
219
+ includedEvents: included.length + 1,
220
+ });
221
+ if (candidate.length > maxChars)
222
+ break;
223
+ included.push(event);
224
+ }
225
+ return JSON.stringify({
226
+ events: included,
227
+ truncated: true,
228
+ totalEvents: filtered.length,
229
+ includedEvents: included.length,
230
+ });
210
231
  }
211
232
  export const DEFAULT_TOOL_REGISTRY = new Map([
212
233
  ['readFile', {
@@ -269,7 +290,7 @@ export const DEFAULT_TOOL_REGISTRY = new Map([
269
290
  ['getToolEvents', {
270
291
  schema: {
271
292
  name: 'getToolEvents',
272
- description: 'Retrieve the agent session tool events as JSON. Optional actionFilter substring-matches against event.action.',
293
+ description: 'Retrieve the agent session tool events as JSON. Optional actionFilter substring-matches against event.action. Oversized responses use a valid { events, truncated, totalEvents, includedEvents } JSON envelope.',
273
294
  input_schema: {
274
295
  type: 'object',
275
296
  properties: { actionFilter: { type: 'string' } },
@@ -2,6 +2,7 @@ import { createAgentSession } from '../types.js';
2
2
  import { createAgentEnvironment } from '../agents/registry.js';
3
3
  import { withAbortTimeout } from '../utils/timeout.js';
4
4
  import { buildModelAgentResultLogEntry } from './agent-result-log.js';
5
+ import { buildToolEventLogEntry } from './tool-event-log.js';
5
6
  import { planRuntimePolicies } from './runtime-policy.js';
6
7
  import { getVisibleAssistantMessage } from './visible-turn.js';
7
8
  import { createAskBus } from './ask-bus/bus.js';
@@ -27,6 +28,7 @@ export function createManagedSession(deps) {
27
28
  ...(deps.opencodeExecutable !== undefined ? { opencodeExecutable: deps.opencodeExecutable } : {}),
28
29
  ...(deps.opencodeMcpToolNames !== undefined ? { opencodeMcpToolNames: deps.opencodeMcpToolNames } : {}),
29
30
  getAbortSignal: () => currentSignal,
31
+ getRemainingMs: () => Math.max(0, deadlineMs - Date.now()),
30
32
  };
31
33
  let session = null;
32
34
  let setupDone = false;
@@ -84,14 +86,15 @@ export function createManagedSession(deps) {
84
86
  messages.push({ role: 'user', content: message });
85
87
  const turnResult = await executeTurn(message);
86
88
  const response = getVisibleAssistantMessage(turnResult);
89
+ const turnCompletedAt = new Date().toISOString();
90
+ for (const toolEvent of turnResult.toolEvents) {
91
+ log.push(buildToolEventLogEntry(toolEvent, turnCompletedAt));
92
+ }
87
93
  log.push(buildModelAgentResultLogEntry({
88
- timestamp: new Date().toISOString(),
94
+ timestamp: turnCompletedAt,
89
95
  turnResult,
90
96
  assistantMessage: response,
91
97
  }));
92
- for (const toolEvent of turnResult.toolEvents) {
93
- log.push({ type: 'tool_event', timestamp: new Date().toISOString(), tool_event: toolEvent });
94
- }
95
98
  messages.push({ role: 'agent', content: response });
96
99
  if (turnResult.exitCode !== 0) {
97
100
  if (turnResult.timedOut)
@@ -2,4 +2,5 @@ import type { LLMPort } from '../utils/llm-types.js';
2
2
  import type { Persona, PersonaConfig } from './types.js';
3
3
  export declare function createPersona(config: PersonaConfig & {
4
4
  llm: LLMPort;
5
+ defaultSummaryModel?: string;
5
6
  }): Persona;
@@ -1,8 +1,12 @@
1
1
  import { createConversationWindow } from './conversation-window.js';
2
2
  export function createPersona(config) {
3
3
  const llm = config.llm;
4
+ const configuredWindowModel = config.conversationWindow === false
5
+ ? undefined
6
+ : config.conversationWindow?.model;
7
+ const summaryModel = config.model ?? configuredWindowModel ?? config.defaultSummaryModel;
4
8
  const window = config.conversationWindow !== false
5
- ? createConversationWindow({ ...config.conversationWindow, model: config.model, llm })
9
+ ? createConversationWindow({ ...config.conversationWindow, model: summaryModel, llm })
6
10
  : null;
7
11
  return {
8
12
  async reply(chat) {
@@ -0,0 +1,3 @@
1
+ import type { ToolEvent } from '../tool-events.js';
2
+ import type { LogEntry } from '../types.js';
3
+ export declare function buildToolEventLogEntry(toolEvent: ToolEvent, fallbackTimestamp: string): LogEntry;
@@ -0,0 +1,7 @@
1
+ export function buildToolEventLogEntry(toolEvent, fallbackTimestamp) {
2
+ return {
3
+ type: 'tool_event',
4
+ timestamp: toolEvent.startedAt ?? fallbackTimestamp,
5
+ tool_event: toolEvent,
6
+ };
7
+ }
@@ -3,13 +3,32 @@ export interface ToolEvent {
3
3
  action: ToolAction;
4
4
  provider: 'claude' | 'codex' | 'cursor' | 'opencode';
5
5
  providerToolName: string;
6
+ /** Provider correlation identifier joining a tool invocation to its result. */
7
+ toolUseId?: string;
6
8
  turnNumber?: number;
7
9
  arguments?: Record<string, unknown>;
10
+ /** Lifecycle state observed by PathGrade. Absent on legacy provider events. */
11
+ status?: 'completed' | 'error' | 'incomplete';
12
+ /** Local wall-clock receive time for the invocation boundary. */
13
+ startedAt?: string;
14
+ /** Local wall-clock receive time for the matching result boundary. */
15
+ completedAt?: string;
16
+ /** Monotonic receive-time delta between invocation and result. */
17
+ durationMs?: number;
18
+ /** Bounded provider result data. Fields remain absent when unavailable. */
19
+ result?: ToolEventResult;
8
20
  summary: string;
9
21
  confidence: 'high' | 'medium' | 'low';
10
22
  rawSnippet: string;
11
23
  skillName?: string;
12
24
  }
25
+ export interface ToolEventResult {
26
+ content?: string;
27
+ stdout?: string;
28
+ stderr?: string;
29
+ exitCode?: number;
30
+ truncated?: boolean;
31
+ }
13
32
  export declare function summarizeToolEvents(events: ToolEvent[]): string;
14
33
  /**
15
34
  * Map from provider-specific tool names to normalized Pathgrade actions.
package/dist/types.d.ts CHANGED
@@ -379,6 +379,8 @@ export interface AgentSessionOptions {
379
379
  abortSignal?: AbortSignal;
380
380
  /** Supplies the current timeout/cancellation signal for reused sessions. */
381
381
  getAbortSignal?: () => AbortSignal | undefined;
382
+ /** Supplies the remaining managed-session deadline for credential validity checks. */
383
+ getRemainingMs?: () => number;
382
384
  /** Absolute pinned runtime path for the OpenCode adapter. */
383
385
  opencodeExecutable?: string;
384
386
  /** Exact generated MCP tool names accepted by the OpenCode event normalizer. */
@@ -1,16 +1,20 @@
1
1
  function getApiKey(env) {
2
- return env?.ANTHROPIC_API_KEY
3
- || env?.APP_ANTHROPIC_API_KEY
4
- || process.env.ANTHROPIC_API_KEY
5
- || process.env.APP_ANTHROPIC_API_KEY;
2
+ return env === undefined
3
+ ? process.env.ANTHROPIC_API_KEY || process.env.APP_ANTHROPIC_API_KEY
4
+ : env.ANTHROPIC_API_KEY || env.APP_ANTHROPIC_API_KEY;
6
5
  }
7
6
  function resolveBaseUrl(env) {
8
- return env?.ANTHROPIC_BASE_URL
9
- || env?.APP_ANTHROPIC_BASE_URL
10
- || process.env.ANTHROPIC_BASE_URL
11
- || process.env.APP_ANTHROPIC_BASE_URL
7
+ return (env === undefined
8
+ ? process.env.ANTHROPIC_BASE_URL || process.env.APP_ANTHROPIC_BASE_URL
9
+ : env.ANTHROPIC_BASE_URL || env.APP_ANTHROPIC_BASE_URL)
12
10
  || 'https://api.anthropic.com';
13
11
  }
12
+ function normalizeModel(model) {
13
+ const normalized = model.trim();
14
+ return normalized.toLowerCase().startsWith('anthropic/')
15
+ ? normalized.slice('anthropic/'.length)
16
+ : normalized;
17
+ }
14
18
  function resolveMessagesUrl(env) {
15
19
  const baseUrl = resolveBaseUrl(env).replace(/\/+$/, '');
16
20
  const apiRoot = baseUrl.endsWith('/v1') ? baseUrl : `${baseUrl}/v1`;
@@ -79,14 +83,14 @@ export const anthropicProvider = {
79
83
  return !!getApiKey(env);
80
84
  },
81
85
  supportsModel(model) {
82
- return model.trim().toLowerCase().startsWith('claude');
86
+ return normalizeModel(model).toLowerCase().startsWith('claude');
83
87
  },
84
88
  async call(prompt, opts) {
85
89
  const apiKey = getApiKey(opts.env);
86
90
  if (!apiKey) {
87
91
  throw new Error('No ANTHROPIC_API_KEY available');
88
92
  }
89
- const model = opts.model || DEFAULT_ANTHROPIC_MODEL;
93
+ const model = normalizeModel(opts.model || DEFAULT_ANTHROPIC_MODEL);
90
94
  const modelConfig = getAnthropicModelConfig(model);
91
95
  assertRawAnthropicModel(model, modelConfig);
92
96
  const temperature = resolveTemperature(model, modelConfig, opts.temperature);
@@ -120,7 +124,7 @@ export const anthropicProvider = {
120
124
  if (!apiKey) {
121
125
  throw new Error('No ANTHROPIC_API_KEY available');
122
126
  }
123
- const model = opts.model || DEFAULT_ANTHROPIC_MODEL;
127
+ const model = normalizeModel(opts.model || DEFAULT_ANTHROPIC_MODEL);
124
128
  const modelConfig = getAnthropicModelConfig(model);
125
129
  assertRawAnthropicModel(model, modelConfig);
126
130
  const temperature = resolveTemperature(model, modelConfig, opts.temperature);
@@ -1,5 +1,11 @@
1
1
  function getApiKey(env) {
2
- return env?.OPENAI_API_KEY || process.env.OPENAI_API_KEY;
2
+ return env === undefined ? process.env.OPENAI_API_KEY : env.OPENAI_API_KEY;
3
+ }
4
+ function normalizeModel(model) {
5
+ const normalized = model.trim();
6
+ return normalized.toLowerCase().startsWith('openai/')
7
+ ? normalized.slice('openai/'.length)
8
+ : normalized;
3
9
  }
4
10
  export const openaiProvider = {
5
11
  name: 'openai',
@@ -7,7 +13,7 @@ export const openaiProvider = {
7
13
  return !!getApiKey(env);
8
14
  },
9
15
  supportsModel(model) {
10
- const normalized = model.trim().toLowerCase();
16
+ const normalized = normalizeModel(model).toLowerCase();
11
17
  return (normalized.startsWith('gpt-')
12
18
  || normalized.startsWith('chatgpt-')
13
19
  || normalized.startsWith('o1')
@@ -19,9 +25,12 @@ export const openaiProvider = {
19
25
  if (!apiKey) {
20
26
  throw new Error('No OPENAI_API_KEY available');
21
27
  }
22
- const model = opts.model || 'gpt-4o';
28
+ const model = normalizeModel(opts.model || 'gpt-4o');
23
29
  const temperature = opts.temperature ?? 0;
24
- const baseUrl = (opts.env?.OPENAI_BASE_URL || process.env.OPENAI_BASE_URL || 'https://api.openai.com/v1').replace(/\/$/, '');
30
+ const configuredBaseUrl = opts.env === undefined
31
+ ? process.env.OPENAI_BASE_URL
32
+ : opts.env.OPENAI_BASE_URL;
33
+ const baseUrl = (configuredBaseUrl || 'https://api.openai.com/v1').replace(/\/$/, '');
25
34
  try {
26
35
  const response = await fetch(`${baseUrl}/chat/completions`, {
27
36
  method: 'POST',
@@ -25,7 +25,7 @@ export declare function createLLMClient(opts: CreateLLMClientOptions): LLMPort;
25
25
  export declare function createLLMClient(providers: LLMProviderAdapter[], agentName?: string): LLMPort;
26
26
  export declare function callLLM(prompt: string, opts?: LLMCallOptions): Promise<LLMCallResult>;
27
27
  /**
28
- * Create an LLM client scoped to the providers that match the given agent.
28
+ * Create an LLM client scoped to the providers that match the given agent and model.
29
29
  *
30
30
  * - claude → CLI + Anthropic API (no OpenAI fallthrough)
31
31
  * - codex → OpenAI API
@@ -33,4 +33,4 @@ export declare function callLLM(prompt: string, opts?: LLMCallOptions): Promise<
33
33
  * If `agentEnv` is provided, it is merged into every LLM call so that
34
34
  * the agent's env propagates to persona/judge/summarization calls.
35
35
  */
36
- export declare function createAgentLLM(agentName: string, agentEnv?: Record<string, string>): LLMPort;
36
+ export declare function createAgentLLM(agentName: string, agentEnv?: Record<string, string>, agentModel?: string): LLMPort;
package/dist/utils/llm.js CHANGED
@@ -17,6 +17,10 @@ function inferProviderFromModel(model) {
17
17
  const normalized = model?.trim().toLowerCase();
18
18
  if (!normalized)
19
19
  return undefined;
20
+ if (normalized.startsWith('anthropic/'))
21
+ return 'anthropic';
22
+ if (normalized.startsWith('openai/'))
23
+ return 'openai';
20
24
  if (normalized.startsWith('claude'))
21
25
  return 'anthropic';
22
26
  if (normalized.startsWith('gpt-')
@@ -178,7 +182,7 @@ export async function callLLM(prompt, opts = {}) {
178
182
  return defaultClient.call(prompt, opts);
179
183
  }
180
184
  /**
181
- * Create an LLM client scoped to the providers that match the given agent.
185
+ * Create an LLM client scoped to the providers that match the given agent and model.
182
186
  *
183
187
  * - claude → CLI + Anthropic API (no OpenAI fallthrough)
184
188
  * - codex → OpenAI API
@@ -186,17 +190,18 @@ export async function callLLM(prompt, opts = {}) {
186
190
  * If `agentEnv` is provided, it is merged into every LLM call so that
187
191
  * the agent's env propagates to persona/judge/summarization calls.
188
192
  */
189
- export function createAgentLLM(agentName, agentEnv) {
193
+ export function createAgentLLM(agentName, agentEnv, agentModel) {
190
194
  // Tool-using judges need a provider that implements callWithTools.
191
195
  // For claude, anthropicProvider is added as a tool-use-capable fallback
192
196
  // alongside the CLI. The CLI still wins for plain call() when available.
193
197
  // Cursor inherits the Claude chain by design (judge consistency across
194
198
  // harnesses — see PRD §"LLM-backend routing"). Cursor evals therefore
195
199
  // depend on Claude CLI or ANTHROPIC_API_KEY being available at judge time.
196
- const baseAdapters = agentName === 'codex'
200
+ const openCodeUsesOpenAI = agentName === 'opencode' && agentModel === 'openai/gpt-5.4';
201
+ const baseAdapters = agentName === 'codex' || openCodeUsesOpenAI
197
202
  ? [openaiProvider]
198
203
  : [cliProvider, anthropicProvider];
199
- const adapters = agentEnv && Object.keys(agentEnv).length > 0
204
+ const adapters = agentEnv !== undefined
200
205
  ? baseAdapters.map((a) => ({
201
206
  ...a,
202
207
  isAvailable: (env) => a.isAvailable({ ...agentEnv, ...env }),
package/dist/viewer.html CHANGED
@@ -1127,10 +1127,10 @@
1127
1127
  + (e.value?.toFixed(2) || '0.00') + '</span>'
1128
1128
  + (e.output ? '<div class="scorer-details" style="margin-top:0.25rem">' + esc(e.output) + '</div>' : '');
1129
1129
  break;
1130
- case 'tool_event':
1131
- body = '<span class="badge badge-type">' + esc(e.tool_event?.action || 'unknown') + '</span> '
1132
- + '<span class="scorer-details">' + esc(e.tool_event?.summary || '') + '</span>';
1133
- break;
1130
+ case 'tool_event': {
1131
+ const tool = e.tool_event || {}; const toolResult = tool.result || {}; const resultText = toolResult.content || toolResult.stdout || ''; const stderrText = toolResult.stderr || ''; const duration = typeof tool.durationMs === 'number' ? `${Math.round(tool.durationMs)}ms` : ''; const exit = typeof toolResult.exitCode === 'number' ? `exit ${toolResult.exitCode}` : ''; const meta = [tool.status || 'unknown', duration, exit].filter(Boolean).join(' · '); const output = resultText || stderrText ? '<div class="log-command-output"><pre class="code-block">' + esc(resultText) + (stderrText ? '<span class="stderr-text">' + esc(stderrText) + '</span>' : '') + '</pre></div>' : '';
1132
+ body = '<details class="log-command-details"><summary class="log-command-summary"><span class="badge badge-type">' + esc(tool.action || 'unknown') + '</span> <span class="scorer-details">' + esc(tool.summary || '') + '</span><span class="log-command-meta">' + esc(meta) + '</span></summary>' + output + '</details>'; break;
1133
+ }
1134
1134
  case 'judge_tool_call': {
1135
1135
  const tc = e.judge_tool_call || {};
1136
1136
  const okBadge = tc.ok
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wix/pathgrade",
3
- "version": "1.0.10",
3
+ "version": "1.0.12",
4
4
  "packageManager": "yarn@4.12.0",
5
5
  "description": "Evaluate whether AI agents discover and use your skills correctly",
6
6
  "exports": {
@@ -132,5 +132,5 @@
132
132
  "typescript": "^5.9.3",
133
133
  "zod": "4.3.6"
134
134
  },
135
- "falconPackageHash": "be877aa61dfb2cd1b58c716b704cffee9592428675a660382e86e358"
135
+ "falconPackageHash": "48e95ba80576ede589f0a5e47d60b9c94fd53c1ef435d8ca9af35392"
136
136
  }