Repository navigation
feat(core): add safe diagnostics and unknown-price warnings #159
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: model-endpoint-overrides
Are you sure you want to change the base?
Changes from all commits
3432d3b
0b7fd9b
e715d85
a117eba
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,34 @@ | ||
| name: Bug report | ||
| description: Report a reproducible provider problem | ||
| title: "Bug: " | ||
| body: | ||
| - type: markdown | ||
| attributes: | ||
| value: "Do not paste API keys, auth files, prompts, private tool results, or full session exports. Use a synthetic reproduction." | ||
| - type: textarea | ||
| id: diagnostics | ||
| attributes: | ||
| label: Safe diagnostics | ||
| description: "Run /commandcode-diagnostics in the failing instance and paste its output. This report excludes prompts, credentials and local paths. If the extension does not load, give the plugin, Pi/OMP, Node/Bun and OS versions instead." | ||
| render: text | ||
| validations: | ||
| required: true | ||
| - type: dropdown | ||
| id: plan | ||
| attributes: | ||
| label: Plan | ||
| options: [GOAT, Pro, Max, Team, Provider, Go, Unknown] | ||
| validations: | ||
| required: true | ||
| - type: textarea | ||
| id: reproduction | ||
| attributes: | ||
| label: Minimal reproduction | ||
| description: "Steps, expected/actual behavior, exact model ID, and whether this followed a model switch, compaction, update or session resume. Use synthetic data." | ||
| validations: | ||
| required: true | ||
| - type: textarea | ||
| id: error | ||
| attributes: | ||
| label: Error and timing | ||
| description: "Share the error code and approximate timestamp. Review/redact any error text yourself; do not include entire request or response bodies." |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -15,6 +15,9 @@ import { | |
| type ProviderConfig, | ||
| } from "@earendil-works/pi-coding-agent" | ||
| import { join } from "node:path" | ||
| import { readFile } from "node:fs/promises" | ||
| import * as hostPackage from "@earendil-works/pi-coding-agent" | ||
| import { createRequestDiagnostics, formatDiagnostics, priceStatus } from "./src/diagnostics.ts" | ||
|
|
||
| import { getConfiguredApiKey } from "./src/api-key.ts" | ||
| import { pickCommandCodeApiKey, withResolvedCommandCodeApiKey } from "./src/converters.ts" | ||
|
|
@@ -197,6 +200,31 @@ function legacyApiBase(providerApiBase: string): string { | |
| } | ||
|
|
||
| export default async function (pi: ExtensionAPI) { | ||
| const packageData: unknown = JSON.parse( | ||
| await readFile(new URL("./package.json", import.meta.url), "utf8"), | ||
| ) | ||
| const pluginVersion = | ||
| typeof packageData === "object" && | ||
| packageData !== null && | ||
| "version" in packageData && | ||
| typeof packageData.version === "string" | ||
| ? packageData.version | ||
| : "unknown" | ||
| const hostVersion = | ||
| "VERSION" in hostPackage && typeof hostPackage.VERSION === "string" | ||
| ? hostPackage.VERSION | ||
| : "unknown" | ||
| const diagnostics = createRequestDiagnostics() | ||
| const warnedPrices = new Set<string>() | ||
| pi.on("before_agent_start", (_event, ctx) => { | ||
| if (ctx.model?.provider !== "commandcode" || warnedPrices.has(ctx.model.id)) return | ||
| if (priceStatus(ctx.model.id) === "known") return | ||
| warnedPrices.add(ctx.model.id) | ||
| ctx.ui.notify( | ||
| "Command Code display price is unknown or needs review. A zero estimate does not mean free usage; check /commandcode-diagnostics.", | ||
| "warning", | ||
| ) | ||
| }) | ||
| // Hosts may select their built-in Command Code model before loading extensions. | ||
| // Rebind that stale selection to our registered transport before the first turn. | ||
| pi.on("session_start", async (_event, ctx) => { | ||
|
|
@@ -320,8 +348,20 @@ export default async function (pi: ExtensionAPI) { | |
| // custom api there so those calls reach the same transport. The registry | ||
| // resolves no credentials for extension providers, so fall back to the | ||
| // configured key when the caller passes none or a placeholder. | ||
| const measuredStream = ( | ||
| model: Parameters<typeof transport.stream>[0], | ||
| context: Parameters<typeof transport.stream>[1], | ||
| options?: Parameters<typeof transport.stream>[2], | ||
| ) => | ||
| diagnostics.stream( | ||
| transport.stream, | ||
| () => new AssistantMessageEventStream(), | ||
| model, | ||
| context, | ||
| resolveStreamOptions(options), | ||
| ) | ||
| const compatStream: CompatStreamFunction = (model, context, options) => | ||
| transport.stream(model, context, resolveStreamOptions(options)) as AssistantMessageEventStream | ||
| measuredStream(model, context, options) as AssistantMessageEventStream | ||
| registerCompatApiProvider(compatStream) | ||
|
|
||
| pi.on("message_end", async (event, ctx) => { | ||
|
|
@@ -349,7 +389,25 @@ export default async function (pi: ExtensionAPI) { | |
| createProviderConfig: (models) => { | ||
| catalogModels.clear() | ||
| for (const model of models) catalogModels.set(model.id, model) | ||
| return createProviderConfig(models, apiBase, transport.stream) | ||
| return createProviderConfig(models, apiBase, measuredStream) | ||
|
Owner
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
| }, | ||
| getDiagnostics: (model) => { | ||
| let route = "not selected" | ||
| if (model?.provider === "commandcode") { | ||
| try { | ||
| route = `${transport.getTransport()}/${resolveModelApi(model.id)} (${routing[model.id] ?? "auto"})` | ||
| } catch { | ||
| route = "invalid routing configuration" | ||
| } | ||
| } | ||
| return formatDiagnostics({ | ||
| pluginVersion, | ||
| host: compatApiProviderRegistrar() ? "pi" : "omp-compatible", | ||
| hostVersion, | ||
| modelId: model?.provider === "commandcode" ? model.id : undefined, | ||
| route, | ||
| request: diagnostics.snapshot(), | ||
| }) | ||
| }, | ||
| getRoutingStatus: (model) => { | ||
| if (routingWarning) return `routing warning: ${routingWarning}` | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -630,7 +630,7 @@ export function createStreamCommandCode(deps: CoreDependencies) { | |
|
|
||
| try { | ||
| try { | ||
| response = await fetchImpl(`${apiBase}/alpha/generate`, { | ||
| response = await (options?.fetch ?? fetchImpl)(`${apiBase}/alpha/generate`, { | ||
|
Owner
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
| method: "POST", | ||
| headers: requestHeaders, | ||
| body: bodyStr, | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This fires on every host start for anyone whose daily model is unpriced — a recurring nag for a condition the user cannot fix. Consider warning once per model per catalog snapshot (persist next to the routing file), or only when the model is actually switched to, rather than on every
before_agent_start.