Skip to content
Merged
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
8 changes: 1 addition & 7 deletions actions/setup/js/parse_mcp_gateway_log.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -609,13 +609,7 @@ function computeToolCallTokenDeltas(tokenUsageContent, requests) {
if (!entry || typeof entry !== "object" || !entry.timestamp) continue;
const ts = new Date(entry.timestamp).getTime();
if (isNaN(ts)) continue;
const et = computeEffectiveTokens(
entry.model || "",
entry.input_tokens || 0,
entry.output_tokens || 0,
entry.cache_read_tokens || 0,
entry.cache_write_tokens || 0
);
const et = computeEffectiveTokens(entry.model || "", entry.input_tokens || 0, entry.output_tokens || 0, entry.cache_read_tokens || 0, entry.cache_write_tokens || 0);
etEntries.push({ ts, et });
} catch {
// skip malformed lines
Expand Down
24 changes: 5 additions & 19 deletions actions/setup/js/parse_mcp_gateway_log.test.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -1537,10 +1537,7 @@ not-json

test("computes delta for a tool call bracketed by two API calls", () => {
// ET(t0) = 1*1000 + 4*50 = 1200; ET(t1) = 1*1500 + 4*80 = 1820; delta = 620
const tokenContent = [
tuLine("2026-05-19T21:10:00.000Z", 1000, 50),
tuLine("2026-05-19T21:10:10.000Z", 1500, 80),
].join("\n");
const tokenContent = [tuLine("2026-05-19T21:10:00.000Z", 1000, 50), tuLine("2026-05-19T21:10:10.000Z", 1500, 80)].join("\n");
const requests = [req("2026-05-19T21:10:05.000Z", "my-tool")];
const deltas = computeToolCallTokenDeltas(tokenContent, requests);
expect(deltas.get(0)).toBe(620);
Expand All @@ -1567,15 +1564,8 @@ not-json

test("computes correct deltas for multiple sequential tool calls", () => {
// ET[0] = 1200, ET[1] = 1820, ET[2] = 2400
const tokenContent = [
tuLine("2026-05-19T21:10:00.000Z", 1000, 50),
tuLine("2026-05-19T21:10:10.000Z", 1500, 80),
tuLine("2026-05-19T21:10:20.000Z", 2000, 100),
].join("\n");
const requests = [
req("2026-05-19T21:10:05.000Z", "tool-a"),
req("2026-05-19T21:10:15.000Z", "tool-b"),
];
const tokenContent = [tuLine("2026-05-19T21:10:00.000Z", 1000, 50), tuLine("2026-05-19T21:10:10.000Z", 1500, 80), tuLine("2026-05-19T21:10:20.000Z", 2000, 100)].join("\n");
const requests = [req("2026-05-19T21:10:05.000Z", "tool-a"), req("2026-05-19T21:10:15.000Z", "tool-b")];
const deltas = computeToolCallTokenDeltas(tokenContent, requests);
expect(deltas.get(0)).toBe(620); // 1820 - 1200
expect(deltas.get(1)).toBe(580); // 2400 - 1820
Expand All @@ -1585,9 +1575,7 @@ not-json
describe("generateRpcMessagesSummary with token deltas", () => {
test("shows ΔET column when deltas are provided", () => {
const entries = {
requests: [
{ timestamp: "2026-05-19T21:10:05.123Z", server_id: "srv", type: "REQUEST", payload: { method: "tools/call", params: { name: "my-tool" } } },
],
requests: [{ timestamp: "2026-05-19T21:10:05.123Z", server_id: "srv", type: "REQUEST", payload: { method: "tools/call", params: { name: "my-tool" } } }],
responses: [],
other: [],
};
Expand All @@ -1600,9 +1588,7 @@ not-json

test("omits ΔET column when no deltas are provided", () => {
const entries = {
requests: [
{ timestamp: "2026-05-19T21:10:05.123Z", server_id: "srv", type: "REQUEST", payload: { method: "tools/call", params: { name: "my-tool" } } },
],
requests: [{ timestamp: "2026-05-19T21:10:05.123Z", server_id: "srv", type: "REQUEST", payload: { method: "tools/call", params: { name: "my-tool" } } }],
responses: [],
other: [],
};
Expand Down
20 changes: 10 additions & 10 deletions pkg/cli/audit_report.go
Original file line number Diff line number Diff line change
Expand Up @@ -181,16 +181,16 @@ type MCPToolSummary struct {

// MCPToolCall represents a single MCP tool call with full details
type MCPToolCall struct {
Timestamp string `json:"timestamp"`
ServerName string `json:"server_name"`
ToolName string `json:"tool_name"`
Method string `json:"method,omitempty"`
InputSize int `json:"input_size"`
OutputSize int `json:"output_size"`
Duration string `json:"duration,omitempty"`
Status string `json:"status"`
Error string `json:"error,omitempty"`
EffectiveTokenDelta int `json:"effective_token_delta,omitempty"` // Change in effective tokens caused by this tool call result
Timestamp string `json:"timestamp"`
ServerName string `json:"server_name"`
ToolName string `json:"tool_name"`
Method string `json:"method,omitempty"`
InputSize int `json:"input_size"`
OutputSize int `json:"output_size"`
Duration string `json:"duration,omitempty"`
Status string `json:"status"`
Error string `json:"error,omitempty"`
EffectiveTokenDelta int `json:"effective_token_delta,omitempty"` // Change in effective tokens caused by this tool call result
}

// MCPServerStats contains server-level statistics
Expand Down
34 changes: 34 additions & 0 deletions pkg/parser/schema_deprecated_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -180,9 +180,43 @@ func TestGetMainWorkflowDeprecatedFieldsDeep(t *testing.T) {
}
}

// infer must be detected with its x-deprecation-message.
infer, ok := byPath["infer"]
if !ok {
t.Error("expected 'infer' in deep deprecated fields, not found")
} else {
if infer.DeprecationMessage == "" {
t.Error("infer: DeprecationMessage should not be empty")
}
}

t.Logf("Found %d deep deprecated fields in schema", len(fields))
}

// TestAllDeprecatedFieldsHaveXDeprecationMessage is a reference test that ensures every
// deprecated field detected by the deep properties walker carries an x-deprecation-message.
// The walker traverses nested "properties" (plus oneOf/anyOf/allOf sub-schemas for property
// discovery) but does not resolve $ref or inspect $defs directly, and does not catch
// deprecated:true set on non-property subschemas (e.g. a deprecated oneOf variant).
// This test therefore enforces the invariant for the set of fields the walker actually emits
// warnings for — fields in $defs or deprecated oneOf variants must be kept consistent manually.
func TestAllDeprecatedFieldsHaveXDeprecationMessage(t *testing.T) {
fields, err := GetMainWorkflowDeprecatedFieldsDeep()
if err != nil {
t.Fatalf("GetMainWorkflowDeprecatedFieldsDeep() error = %v", err)
}

for _, f := range fields {
if f.DeprecationMessage == "" {
t.Errorf("schema field %q has deprecated:true but no x-deprecation-message — "+
"add an x-deprecation-message to the schema entry so users receive an "+
"actionable migration hint", f.Path)
}
}

t.Logf("Verified %d deprecated fields all have x-deprecation-message", len(fields))
}

func TestCollectDeprecatedDeep(t *testing.T) {
// Build a minimal schema that exercises nesting and oneOf traversal.
schema := map[string]any{
Expand Down
25 changes: 19 additions & 6 deletions pkg/parser/schemas/main_workflow_schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -2996,7 +2996,9 @@
},
"infer": {
"type": "boolean",
"deprecated": true,
"description": "DEPRECATED: Use 'disable-model-invocation' instead. Controls whether the custom agent should infer additional context from the conversation. This field is maintained for backward compatibility with existing custom agent files.",
"x-deprecation-message": "'infer' is deprecated. Use 'disable-model-invocation' instead.",
"examples": [false]
},
"disable-model-invocation": {
Expand Down Expand Up @@ -5064,6 +5066,7 @@
"type": "object",
"description": "DEPRECATED: Use 'create-agent-session' instead. Configuration for creating GitHub Copilot coding agent sessions from agentic workflow output using gh agent-task CLI. The main job does not need write permissions.",
"deprecated": true,
"x-deprecation-message": "'create-agent-task' is deprecated. Use 'create-agent-session' instead.",
"properties": {
"base": {
"type": "string",
Expand Down Expand Up @@ -5657,7 +5660,8 @@
"title-prefix": {
"type": "string",
"description": "Deprecated alias for required-title-prefix",
"deprecated": true
"deprecated": true,
"x-deprecation-message": "'title-prefix' is deprecated in safe-outputs.close-discussion. Use 'required-title-prefix' instead."
},
"required-category": {
"type": "string",
Expand Down Expand Up @@ -5802,7 +5806,8 @@
"title-prefix": {
"type": "string",
"description": "Deprecated alias for required-title-prefix",
"deprecated": true
"deprecated": true,
"x-deprecation-message": "'title-prefix' is deprecated in safe-outputs.close-issue. Use 'required-title-prefix' instead."
},
"target": {
"type": "string",
Expand Down Expand Up @@ -5884,7 +5889,8 @@
"title-prefix": {
"type": "string",
"description": "Deprecated alias for required-title-prefix",
"deprecated": true
"deprecated": true,
"x-deprecation-message": "'title-prefix' is deprecated in safe-outputs.close-pull-request. Use 'required-title-prefix' instead."
},
"target": {
"type": "string",
Expand Down Expand Up @@ -5957,7 +5963,8 @@
"title-prefix": {
"type": "string",
"description": "Deprecated alias for required-title-prefix",
"deprecated": true
"deprecated": true,
"x-deprecation-message": "'title-prefix' is deprecated in safe-outputs.mark-pull-request-as-ready-for-review. Use 'required-title-prefix' instead."
},
"target": {
"type": "string",
Expand Down Expand Up @@ -7056,6 +7063,7 @@
"type": "array",
"description": "Deprecated alias for allowed-reviewers",
"deprecated": true,
"x-deprecation-message": "'reviewers' is deprecated in safe-outputs.add-reviewer. Use 'allowed-reviewers' instead.",
"items": {
"type": "string"
},
Expand All @@ -7081,6 +7089,7 @@
"team-reviewers": {
"description": "Deprecated alias for allowed-team-reviewers",
"deprecated": true,
"x-deprecation-message": "'team-reviewers' is deprecated in safe-outputs.add-reviewer. Use 'allowed-team-reviewers' instead.",
"oneOf": [
{
"type": "string"
Expand Down Expand Up @@ -7737,7 +7746,8 @@
"type": "string"
},
"description": "Deprecated alias for required-labels",
"deprecated": true
"deprecated": true,
"x-deprecation-message": "'allowed-labels' is deprecated in safe-outputs.merge-pull-request. Use 'required-labels' instead."
},
"allowed-branches": {
"type": "array",
Expand Down Expand Up @@ -7806,7 +7816,8 @@
"title-prefix": {
"type": "string",
"description": "Deprecated alias for required-title-prefix",
"deprecated": true
"deprecated": true,
"x-deprecation-message": "'title-prefix' is deprecated in safe-outputs.push-to-pull-request-branch. Use 'required-title-prefix' instead."
},
"required-labels": {
"description": "Required labels for pull request validation. Only pull requests with all these labels will be accepted. Accepts an array of label names or a GitHub Actions expression resolving to a comma-separated list of labels (e.g. '${{ inputs[\\'required-labels\\'] }}').",
Expand All @@ -7828,6 +7839,7 @@
"labels": {
"description": "Deprecated alias for required-labels",
"deprecated": true,
"x-deprecation-message": "'labels' is deprecated in safe-outputs.push-to-pull-request-branch. Use 'required-labels' instead.",
"oneOf": [
{
"type": "array",
Expand Down Expand Up @@ -11013,6 +11025,7 @@
"network": {
"type": "object",
"deprecated": true,
"x-deprecation-message": "Per-MCP-server 'network' configuration is deprecated. Use the top-level workflow 'network:' field instead.",
"$comment": "DEPRECATED: Per-server network configuration is no longer supported. Use top-level workflow 'network:' configuration instead.",
"properties": {
"allowed": {
Expand Down
35 changes: 35 additions & 0 deletions pkg/workflow/compiler_orchestrator_tools_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -825,3 +825,38 @@ func TestWarnDeprecatedFrontmatterFields_MultipleFields(t *testing.T) {
assert.Contains(t, stderr, "serena", "warning should mention serena")
assert.Equal(t, 2, compiler.warningCount, "one warning per deprecated field")
}

// TestWarnDeprecatedFrontmatterFields_Infer verifies that using the deprecated
// top-level 'infer' field emits the schema-driven deprecation warning pointing
// to 'disable-model-invocation'.
func TestWarnDeprecatedFrontmatterFields_Infer(t *testing.T) {
compiler := NewCompiler()
frontmatter := map[string]any{
"infer": false,
}
stderr := captureStderr(func() {
compiler.warnDeprecatedFrontmatterFields(frontmatter)
})

assert.Contains(t, stderr, "disable-model-invocation", "warning should mention the replacement field")
assert.Equal(t, 1, compiler.warningCount, "one warning for infer")
}

// TestWarnDeprecatedFrontmatterFields_SafeOutputsDeprecatedAliases verifies that
// deprecated safe-outputs aliases (e.g. title-prefix) emit schema-driven warnings.
func TestWarnDeprecatedFrontmatterFields_SafeOutputsDeprecatedAliases(t *testing.T) {
compiler := NewCompiler()
frontmatter := map[string]any{
"safe-outputs": map[string]any{
"close-issue": map[string]any{
"title-prefix": "[old] ",
},
},
}
stderr := captureStderr(func() {
compiler.warnDeprecatedFrontmatterFields(frontmatter)
})

assert.Contains(t, stderr, "required-title-prefix", "warning should mention required-title-prefix replacement")
assert.Equal(t, 1, compiler.warningCount, "one warning for the deprecated alias")
}