Repository navigation
Add a Portkey key type and explain invalid providers.json saves - #131
Merged
Merged
Conversation
mutateProvidersConfig now rejects with ProvidersConfigInvalidError when the existing file has a JSONC syntax error or fails schema validation, or when the mutation result would be invalid. The error carries the file path, a phase (existing-file or proposed-result), and either the syntax position or Zod issue paths and messages, so hosts can tell users why a save failed without forwarding raw exceptions that might contain credentials.
Hosts carry the diagnostic across browser boundaries, so the detail, phase, and issue shapes belong in the browser-safe entry rather than only beside the node-only error class.
providers.portkey.keyType ("portkey" | "upstream", env PORTKEY_KEY_TYPE)
says what the stored API key is, instead of inferring it from whether the
URL is api.portkey.ai. Absent keeps today's URL inference.
- The schema declares only the enum. Cross-field rules live in the pure
checkPortkeyConnection({ baseUrl, keyType, apiKeyPresent }), so a bad
combination never makes providers.json invalid.
- inferredPortkeyKeyType(baseUrl) lets writers omit a keyType equal to what
the URL implies, so older strict readers keep the block.
- The resolved connection carries keyType, with per-field provenance for
keyType and baseUrl so forms can pin env- and admin-owned fields.
- ApiKeyCredentials gains a runtime-only portkey.keyType, never persisted;
the shape guard pins its vocabulary to ai-config's.
With keyType "portkey" on a URL other than api.portkey.ai, the Portkey provider now treats the gateway as one that takes Portkey keys (a corporate proxy in front of hosted Portkey, or a Portkey hybrid gateway): - sends only x-portkey-api-key; delegates get an empty native key so Authorization and x-api-key are stripped - uses the base URL verbatim, without appending /v1 - injects no x-portkey-provider routing header - accepts both bare and @slug/model ids - discovers models from <url>/models with Portkey auth Validation now goes through ai-config's checkPortkeyConnection, and the chat same-gateway check uses a mode-aware samePortkeyGateway so the raw provider URL the catalog passes per request still matches. Configs without a key type behave exactly as before.
Zod 4's invalid_type message names the received value's type, and for an object without Object.prototype (jsonc parses into those) that is the file-controlled constructor.name. A secret placed there could reach the UI and logs through the typed invalid-config error. schemaIssues now rebuilds invalid_type messages from the expected type alone. Other built-in messages name only schema values or key names.
connectionProvenance.portkey.keyTypeAfterUserClear is the key type the source stack yields with the user layer removed (an admin default or a pin). Writers need it to tell whether omitting the user's keyType keeps its meaning, and the winning source alone hides it once the user file sets a value. Also: - export isPinnedConnectionFieldSource, the one "environment or enforced" predicate for pinned connection fields - collapse the Portkey gateway URL helpers into one mode-aware function and stop exporting samePortkeyGateway - test that gateway mode refuses a per-model base URL for another host, including the same URL with /v1 appended - name the invalid PORTKEY_KEY_TYPE test for what it does: the whole env source is dropped and reported - update comments and the hosted-mode model id error that still said the URL alone decides the key type
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
This PR makes two independent improvements:
keyTypesetting for the built-in Portkey provider says whether the API key is a Portkey API key ("portkey") or the upstream provider's key ("upstream"), so that Portkey works through a corporate proxy in front of hosted Portkey and through Portkey hybrid or enterprise gateways.Today the base URL decides what the Portkey key means:
https://api.portkey.aimeans a Portkey key, and any other URL means an upstream provider's key. A user whose company routes hosted Portkey through its own proxy has a Portkey key on a non-Portkey URL. The provider treated that key as an upstream key: it added/v1to the URL, sent the key in the upstream's auth header, and injected a routing header, so every request failed.With key type
"portkey"on any URL other than hosted Portkey, the provider now matches the request a user would send with curl:/is trimmed). Users include/v1if their gateway needs it.x-portkey-api-key, with noAuthorizationorx-api-keyheader.x-portkey-providerrouting header.claude-sonnet-4-6) and catalog IDs (@anthropic/claude-sonnet-4-6).<base URL>/models. If that request fails, the existing cache and declared-models fallback applies.When
keyTypeis unset, the key type is inferred from the URL exactly as before, so existing configs behave the same. The key type can also come from thePORTKEY_KEY_TYPEenvironment variable or an administrator's config, and the resolved catalog reports where it came from so forms can show it read-only.One shared check owns the rules that combine the base URL, key type, and key presence. The provider runs it on the effective connection before each request, and it's exported so that hosts can run it at save time. It reports a missing base URL, the
api.portkey.aihost on anything other than exactlyhttps://api.portkey.ai, key type"upstream"with the hosted URL, and key type"portkey"with no key. It takes only whether a key is present, never the key itself. These rules deliberately live outside the strict schema, so a bad combination can never make providers.json invalid and block every later save.The invalid-file error distinguishes a file that is already invalid from a change that would make it invalid. For a syntax error it carries the line and column; for a schema error it carries each problem's path and message. Messages never include the values found in the file, so a secret in a custom header can't leak into an error shown to the user.
Changes
providers.portkey.keyType("portkey"or"upstream"), and the published JSON schema describes it.Not included
type: "portkey"entries don't getkeyType; they keep the URL-based behavior. That needs changes to the custom schema, forms, and discovery policy, and is a follow-up.<base URL>/modelsand header-only auth through the customer's proxy haven't been confirmed against the real proxy yet. If they don't hold, the gateway mode needs rework before release.Breaking changes
Older clients share
~/.posit/ai/providers.json, and their strict schema skips aportkeyblock that setskeyType. The monorepo's writers therefore writekeyTypeonly when it differs from the key type the URL implies, so hosted users keep a config older clients can read.