Skip to content

Agents: log proxy route summary#64754

Merged
ImLukeF merged 4 commits intomainfrom
imlukef/proxy-endpoint-diagnostics
Apr 11, 2026
Merged

Agents: log proxy route summary#64754
ImLukeF merged 4 commits intomainfrom
imlukef/proxy-endpoint-diagnostics

Conversation

@ImLukeF
Copy link
Copy Markdown
Contributor

@ImLukeF ImLukeF commented Apr 11, 2026

Summary

  • add a compact provider routing summary helper for native, local, custom, and proxy-like OpenAI-compatible endpoints
  • include the resolved routing summary in the embedded runner's existing prompt-start debug log
  • add focused attribution tests covering proxy-like, local, native OpenAI, and OpenRouter summaries

Testing

  • pnpm test src/agents/provider-attribution.test.ts
  • pnpm test src/agents/pi-embedded-runner/run/attempt.test.ts

@openclaw-barnacle openclaw-barnacle bot added agents Agent runtime and tooling size: S maintainer Maintainer-authored PR labels Apr 11, 2026
@ImLukeF ImLukeF marked this pull request as ready for review April 11, 2026 10:39
Copilot AI review requested due to automatic review settings April 11, 2026 10:39
@greptile-apps
Copy link
Copy Markdown
Contributor

greptile-apps bot commented Apr 11, 2026

Greptile Summary

Adds a compact routing-summary function to the provider attribution module and wires it into the embedded-runner debug log at prompt start. Two private classification helpers compute a readable label string; tests cover four routing shapes (custom, local, native, and OpenRouter).

Confidence Score: 5/5

Safe to merge; all findings are minor style/P2 concerns with no correctness or data-integrity issues.

The change is a pure logging/diagnostic addition — no runtime behavior is altered. The three P2 comments cover a potentially misleading proxy-like label for known native vendors, an ambiguous log-format convention, and missing test coverage for two edge-case route classes. None block correctness or production safety.

No files require special attention.

Prompt To Fix All With AI
This is a comment left during a code review.
Path: src/agents/provider-attribution.ts
Line: 638-654

Comment:
**"proxy-like" label applied to non-OpenAI native vendors**

`usesExplicitProxyLikeEndpoint` is `true` for any configured base URL that isn't one of the three OpenAI-family native endpoints (`openai-public`, `openai-codex`, `azure-openai`). This means known native vendor hosts — Groq (`api.groq.com`), Anthropic (`api.anthropic.com`), xAI, Mistral, etc. — all surface as `route=proxy-like` in the new log line, even though the `endpoint=groq-native` / `endpoint=anthropic-public` field correctly identifies them as native.

The mismatch can trip up log readers: `endpoint=groq-native route=proxy-like` looks contradictory. Since this is baked into the existing `usesExplicitProxyLikeEndpoint` semantics, the fix belongs in `describeProviderRequestRouteClass` — either use the broader `isKnownNativeEndpoint` signal, or add a comment explaining why only OpenAI-family endpoints count as "native" in this routing context.

How can I resolve this? If you propose a fix, please make it concise.

---

This is a comment left during a code review.
Path: src/agents/pi-embedded-runner/run/attempt.ts
Line: 1734-1737

Comment:
**Log format nests multi-key string under a single `routing=` prefix**

The current output looks like `routing=provider=openai api=openai-responses ...`, which is ambiguous for log aggregators (Loki, Datadog, grep) that split on the first `=`. Inlining the fields directly at the top level is the more machine-friendly convention:

```suggestion
        log.debug(
          `embedded run prompt start: runId=${params.runId} sessionId=${params.sessionId} ` +
            routingSummary,
        );
```

How can I resolve this? If you propose a fix, please make it concise.

---

This is a comment left during a code review.
Path: src/agents/provider-attribution.test.ts
Line: 307-351

Comment:
**Missing coverage for "default" and "invalid" route classes**

The new test covers `custom` (proxy-like), `local`, `openai-public` (native), and `openrouter` (proxy-like), but the `"default"` (no `baseUrl`) and `"invalid"` (malformed `baseUrl`) branches of `describeProviderRequestRouteClass` are unexercised. Two extra assertions would close the gap:

```ts
// default — no baseUrl
expect(
  describeProviderRequestRoutingSummary({ provider: "openai", api: "openai-responses" }),
).toBe("provider=openai api=openai-responses endpoint=default route=default policy=none");

// invalid — unparseable baseUrl
expect(
  describeProviderRequestRoutingSummary({
    provider: "openai",
    api: "openai-responses",
    baseUrl: "javascript:alert(1)",
  }),
).toBe("provider=openai api=openai-responses endpoint=invalid route=invalid policy=none");
```

How can I resolve this? If you propose a fix, please make it concise.

Reviews (1): Last reviewed commit: "Agents: log proxy route summary" | Re-trigger Greptile

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Adds a compact, human-readable routing summary for provider requests (native/local/custom/proxy-like), and includes that summary in the embedded runner’s prompt-start debug logging to improve observability of how requests are being routed.

Changes:

  • Introduce describeProviderRequestRoutingSummary() in provider-attribution.ts.
  • Log the routing summary at embedded prompt start in pi-embedded-runner.
  • Add unit tests validating routing summaries for common endpoint patterns (proxy-like, local, native OpenAI, OpenRouter).

Reviewed changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated 3 comments.

File Description
src/agents/provider-attribution.ts Adds routing summary helper + internal route/policy descriptor helpers.
src/agents/provider-attribution.test.ts Adds focused assertions for routing summary output.
src/agents/pi-embedded-runner/run/attempt.ts Adds routing summary to the existing prompt-start debug log.

Comment on lines +638 to +654
function describeProviderRequestRouteClass(
policy: ProviderRequestPolicyResolution,
): "default" | "native" | "proxy-like" | "local" | "invalid" {
if (policy.endpointClass === "default") {
return "default";
}
if (policy.endpointClass === "invalid") {
return "invalid";
}
if (policy.endpointClass === "local") {
return "local";
}
if (policy.usesExplicitProxyLikeEndpoint) {
return "proxy-like";
}
return "native";
}
Copy link

Copilot AI Apr 11, 2026

Choose a reason for hiding this comment

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

describeProviderRequestRouteClass() uses usesExplicitProxyLikeEndpoint to decide proxy-like vs native. However usesExplicitProxyLikeEndpoint is currently true for any non-default baseUrl that isn't a known OpenAI endpoint (see resolveProviderRequestPolicy: usesConfiguredBaseUrl && !usesKnownNativeOpenAIEndpoint), which includes known native vendor endpoints like mistral-public, xai-native, google-*, etc. This will mislabel many native routes as proxy-like in the new routing summary. Consider basing proxy-like on endpointClass (e.g. custom and openrouter) and treating all other non-default/non-local/non-invalid endpoint classes as native.

Copilot uses AI. Check for mistakes.
Comment on lines +307 to +351
it("summarizes proxy-like, local, and native routing compactly", () => {
expect(
describeProviderRequestRoutingSummary({
provider: "openai",
api: "openai-responses",
baseUrl: "https://proxy.example.com/v1",
transport: "stream",
capability: "llm",
}),
).toBe("provider=openai api=openai-responses endpoint=custom route=proxy-like policy=none");

expect(
describeProviderRequestRoutingSummary({
provider: "qwen",
api: "openai-responses",
baseUrl: "http://localhost:1234/v1",
transport: "stream",
capability: "llm",
}),
).toBe("provider=qwen api=openai-responses endpoint=local route=local policy=none");

expect(
describeProviderRequestRoutingSummary({
provider: "openai",
api: "openai-responses",
baseUrl: "https://api.openai.com/v1",
transport: "stream",
capability: "llm",
}),
).toBe(
"provider=openai api=openai-responses endpoint=openai-public route=native policy=hidden",
);

expect(
describeProviderRequestRoutingSummary({
provider: "openrouter",
api: "openai-responses",
baseUrl: "https://openrouter.ai/api/v1",
transport: "stream",
capability: "llm",
}),
).toBe(
"provider=openrouter api=openai-responses endpoint=openrouter route=proxy-like policy=documented",
);
});
Copy link

Copilot AI Apr 11, 2026

Choose a reason for hiding this comment

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

The new helper has a unit test for OpenAI/OpenRouter/local/custom, but it doesn’t cover other known native endpoint classes (e.g. mistral-public, xai-native, google-generative-ai, etc). Given the current route classification logic, adding at least one assertion for a non-OpenAI native endpoint would prevent regressions where native endpoints are summarized as proxy-like.

Copilot uses AI. Check for mistakes.
Comment on lines +1727 to +1737
const routingSummary = describeProviderRequestRoutingSummary({
provider: params.provider,
api: params.model.api,
baseUrl: params.model.baseUrl,
capability: "llm",
transport: "stream",
});
log.debug(
`embedded run prompt start: runId=${params.runId} sessionId=${params.sessionId} ` +
`routing=${routingSummary}`,
);
Copy link

Copilot AI Apr 11, 2026

Choose a reason for hiding this comment

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

describeProviderRequestRoutingSummary() does policy resolution work on every prompt start even when debug logs are disabled. Since this path runs per turn and the file already uses log.isEnabled("debug") for heavier debug-only computation, consider guarding the routingSummary computation and the debug log with the same check to avoid unnecessary overhead.

Suggested change
const routingSummary = describeProviderRequestRoutingSummary({
provider: params.provider,
api: params.model.api,
baseUrl: params.model.baseUrl,
capability: "llm",
transport: "stream",
});
log.debug(
`embedded run prompt start: runId=${params.runId} sessionId=${params.sessionId} ` +
`routing=${routingSummary}`,
);
if (log.isEnabled("debug")) {
const routingSummary = describeProviderRequestRoutingSummary({
provider: params.provider,
api: params.model.api,
baseUrl: params.model.baseUrl,
capability: "llm",
transport: "stream",
});
log.debug(
`embedded run prompt start: runId=${params.runId} sessionId=${params.sessionId} ` +
`routing=${routingSummary}`,
);
}

Copilot uses AI. Check for mistakes.
Comment on lines +638 to +654
function describeProviderRequestRouteClass(
policy: ProviderRequestPolicyResolution,
): "default" | "native" | "proxy-like" | "local" | "invalid" {
if (policy.endpointClass === "default") {
return "default";
}
if (policy.endpointClass === "invalid") {
return "invalid";
}
if (policy.endpointClass === "local") {
return "local";
}
if (policy.usesExplicitProxyLikeEndpoint) {
return "proxy-like";
}
return "native";
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P2 "proxy-like" label applied to non-OpenAI native vendors

usesExplicitProxyLikeEndpoint is true for any configured base URL that isn't one of the three OpenAI-family native endpoints (openai-public, openai-codex, azure-openai). This means known native vendor hosts — Groq (api.groq.com), Anthropic (api.anthropic.com), xAI, Mistral, etc. — all surface as route=proxy-like in the new log line, even though the endpoint=groq-native / endpoint=anthropic-public field correctly identifies them as native.

The mismatch can trip up log readers: endpoint=groq-native route=proxy-like looks contradictory. Since this is baked into the existing usesExplicitProxyLikeEndpoint semantics, the fix belongs in describeProviderRequestRouteClass — either use the broader isKnownNativeEndpoint signal, or add a comment explaining why only OpenAI-family endpoints count as "native" in this routing context.

Prompt To Fix With AI
This is a comment left during a code review.
Path: src/agents/provider-attribution.ts
Line: 638-654

Comment:
**"proxy-like" label applied to non-OpenAI native vendors**

`usesExplicitProxyLikeEndpoint` is `true` for any configured base URL that isn't one of the three OpenAI-family native endpoints (`openai-public`, `openai-codex`, `azure-openai`). This means known native vendor hosts — Groq (`api.groq.com`), Anthropic (`api.anthropic.com`), xAI, Mistral, etc. — all surface as `route=proxy-like` in the new log line, even though the `endpoint=groq-native` / `endpoint=anthropic-public` field correctly identifies them as native.

The mismatch can trip up log readers: `endpoint=groq-native route=proxy-like` looks contradictory. Since this is baked into the existing `usesExplicitProxyLikeEndpoint` semantics, the fix belongs in `describeProviderRequestRouteClass` — either use the broader `isKnownNativeEndpoint` signal, or add a comment explaining why only OpenAI-family endpoints count as "native" in this routing context.

How can I resolve this? If you propose a fix, please make it concise.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Addressed in 7412638cde. The route classifier now treats only custom and openrouter endpoints as proxy-like; known vendor-native endpoint classes now summarize as route=native.

Comment on lines +1734 to +1737
log.debug(
`embedded run prompt start: runId=${params.runId} sessionId=${params.sessionId} ` +
`routing=${routingSummary}`,
);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P2 Log format nests multi-key string under a single routing= prefix

The current output looks like routing=provider=openai api=openai-responses ..., which is ambiguous for log aggregators (Loki, Datadog, grep) that split on the first =. Inlining the fields directly at the top level is the more machine-friendly convention:

Suggested change
log.debug(
`embedded run prompt start: runId=${params.runId} sessionId=${params.sessionId} ` +
`routing=${routingSummary}`,
);
log.debug(
`embedded run prompt start: runId=${params.runId} sessionId=${params.sessionId} ` +
routingSummary,
);
Prompt To Fix With AI
This is a comment left during a code review.
Path: src/agents/pi-embedded-runner/run/attempt.ts
Line: 1734-1737

Comment:
**Log format nests multi-key string under a single `routing=` prefix**

The current output looks like `routing=provider=openai api=openai-responses ...`, which is ambiguous for log aggregators (Loki, Datadog, grep) that split on the first `=`. Inlining the fields directly at the top level is the more machine-friendly convention:

```suggestion
        log.debug(
          `embedded run prompt start: runId=${params.runId} sessionId=${params.sessionId} ` +
            routingSummary,
        );
```

How can I resolve this? If you propose a fix, please make it concise.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Addressed in 7412638cde. The prompt-start log now appends the routing summary fields directly instead of nesting them behind routing=.

Comment on lines +307 to +351
it("summarizes proxy-like, local, and native routing compactly", () => {
expect(
describeProviderRequestRoutingSummary({
provider: "openai",
api: "openai-responses",
baseUrl: "https://proxy.example.com/v1",
transport: "stream",
capability: "llm",
}),
).toBe("provider=openai api=openai-responses endpoint=custom route=proxy-like policy=none");

expect(
describeProviderRequestRoutingSummary({
provider: "qwen",
api: "openai-responses",
baseUrl: "http://localhost:1234/v1",
transport: "stream",
capability: "llm",
}),
).toBe("provider=qwen api=openai-responses endpoint=local route=local policy=none");

expect(
describeProviderRequestRoutingSummary({
provider: "openai",
api: "openai-responses",
baseUrl: "https://api.openai.com/v1",
transport: "stream",
capability: "llm",
}),
).toBe(
"provider=openai api=openai-responses endpoint=openai-public route=native policy=hidden",
);

expect(
describeProviderRequestRoutingSummary({
provider: "openrouter",
api: "openai-responses",
baseUrl: "https://openrouter.ai/api/v1",
transport: "stream",
capability: "llm",
}),
).toBe(
"provider=openrouter api=openai-responses endpoint=openrouter route=proxy-like policy=documented",
);
});
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P2 Missing coverage for "default" and "invalid" route classes

The new test covers custom (proxy-like), local, openai-public (native), and openrouter (proxy-like), but the "default" (no baseUrl) and "invalid" (malformed baseUrl) branches of describeProviderRequestRouteClass are unexercised. Two extra assertions would close the gap:

// default — no baseUrl
expect(
  describeProviderRequestRoutingSummary({ provider: "openai", api: "openai-responses" }),
).toBe("provider=openai api=openai-responses endpoint=default route=default policy=none");

// invalid — unparseable baseUrl
expect(
  describeProviderRequestRoutingSummary({
    provider: "openai",
    api: "openai-responses",
    baseUrl: "javascript:alert(1)",
  }),
).toBe("provider=openai api=openai-responses endpoint=invalid route=invalid policy=none");
Prompt To Fix With AI
This is a comment left during a code review.
Path: src/agents/provider-attribution.test.ts
Line: 307-351

Comment:
**Missing coverage for "default" and "invalid" route classes**

The new test covers `custom` (proxy-like), `local`, `openai-public` (native), and `openrouter` (proxy-like), but the `"default"` (no `baseUrl`) and `"invalid"` (malformed `baseUrl`) branches of `describeProviderRequestRouteClass` are unexercised. Two extra assertions would close the gap:

```ts
// default — no baseUrl
expect(
  describeProviderRequestRoutingSummary({ provider: "openai", api: "openai-responses" }),
).toBe("provider=openai api=openai-responses endpoint=default route=default policy=none");

// invalid — unparseable baseUrl
expect(
  describeProviderRequestRoutingSummary({
    provider: "openai",
    api: "openai-responses",
    baseUrl: "javascript:alert(1)",
  }),
).toBe("provider=openai api=openai-responses endpoint=invalid route=invalid policy=none");
```

How can I resolve this? If you propose a fix, please make it concise.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Addressed in 7412638cde. Added assertions for the default and invalid route classes, plus a known-native Groq case so the route label stays aligned with the endpoint class.

@aisle-research-bot
Copy link
Copy Markdown

aisle-research-bot bot commented Apr 11, 2026

🔒 Aisle Security Analysis

We found 1 potential security issue(s) in this PR:

# Severity Title
1 🔵 Low Log forging (CRLF injection) via unsanitized provider/api values in embedded-agent debug logs
1. 🔵 Log forging (CRLF injection) via unsanitized provider/api values in embedded-agent debug logs
Property Value
Severity Low
CWE CWE-117
Location src/agents/provider-attribution.ts:656-672

Description

describeProviderRequestRoutingSummary() formats provider and api directly into a space-delimited log fragment that is appended to a debug log line in the embedded runner.

  • input.api is only normalized via trim() + toLowerCase() (normalizeOptionalLowercaseString) and does not remove control characters like \r/\n.
  • provider is derived from input.provider via normalizeProviderId() which also only lowercases/trims and does not strip embedded newlines.
  • The resulting string is concatenated into log.debug(...) in runEmbeddedAttempt, so if an attacker can influence params.provider or params.model.api, they can inject newline characters to forge additional log lines or corrupt structured log parsing.

Vulnerable code:

return [
  `provider=${provider}`,
  `api=${api}`,
  ...
].join(" ");

Recommendation

Sanitize/escape control characters before including these values in log messages, or (preferably) log them as structured fields so the logger handles escaping.

Example sanitization helper:

function sanitizeForLog(value: string): string {// Replace ASCII control chars (0x00-0x1F, 0x7F) with safe escapes
  return value.replace(/[\x00-\x1F\x7F]/g, (ch) => {
    switch (ch) {
      case "\n": return "\\n";
      case "\r": return "\\r";
      case "\t": return "\\t";
      default: return `\\x${ch.charCodeAt(0).toString(16).padStart(2, "0")}`;
    }
  });
}

const api = sanitizeForLog(normalizeOptionalLowercaseString(input.api) ?? "unknown");
const provider = sanitizeForLog(policy.provider ?? "unknown");

Or, if the logger supports it, switch to structured logging:

log.debug({ runId, sessionId, provider, api, endpoint: policy.endpointClass, route: routeClass, policy: routingPolicy },
  "embedded run prompt start");

Analyzed PR: #64754 at commit 3a668e9

Last updated on: 2026-04-11T11:38:25Z

@ImLukeF ImLukeF force-pushed the imlukef/proxy-endpoint-diagnostics branch from c9731a5 to 3a668e9 Compare April 11, 2026 11:30
@ImLukeF ImLukeF merged commit d7479dc into main Apr 11, 2026
8 checks passed
@ImLukeF ImLukeF deleted the imlukef/proxy-endpoint-diagnostics branch April 11, 2026 11:31
@ImLukeF
Copy link
Copy Markdown
Contributor Author

ImLukeF commented Apr 11, 2026

Merged via squash.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

agents Agent runtime and tooling maintainer Maintainer-authored PR size: S

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants