ghc-proxy 0.7.0 → 0.7.1
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.
- package/README.md +25 -0
- package/dist/main.mjs +72 -1
- package/dist/main.mjs.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -195,6 +195,8 @@ All fields are optional. The full schema:
|
|
|
195
195
|
| `responsesApiAutoCompactInput` | `boolean` | `false` | Automatically trim Responses `input` to the latest `compaction` item |
|
|
196
196
|
| `responsesApiAutoContextManagement` | `boolean` | `false` | Automatically inject Responses `context_management` for selected models |
|
|
197
197
|
| `responsesApiContextManagementModels` | `string[]` | -- | Models eligible for auto-injected Responses `context_management` |
|
|
198
|
+
| `responsesApiParameterFilters` | `{ models, params }[]` | -- | Extra rules to strip request parameters on the Responses boundary (see [Responses Parameter Filters](#responses-parameter-filters)) |
|
|
199
|
+
| `responsesApiParameterFiltersReplaceDefault` | `boolean` | `false` | Disable the built-in reasoning-model default rule so only your `responsesApiParameterFilters` apply |
|
|
198
200
|
| `responsesOfficialEmulator` | `boolean` | `false` | Enable local OpenAI-style Responses state emulation for `previous_response_id`, `conversation`, retrieve, input_items, delete, and input_tokens |
|
|
199
201
|
| `responsesOfficialEmulatorTtlSeconds` | `number` | `14400` | In-memory TTL for locally emulated Responses state |
|
|
200
202
|
| `modelReasoningEfforts` | `Record<string, string>` | -- | Per-model reasoning effort defaults for Anthropic-to-Responses translation |
|
|
@@ -381,6 +383,7 @@ This keeps the existing chat pipeline stable while allowing newer Copilot models
|
|
|
381
383
|
- automatic Responses `context_management` injection is disabled by default and only applies when `responsesApiAutoContextManagement` is `true` and the model matches `responsesApiContextManagementModels`
|
|
382
384
|
- automatic trimming of Responses `input` to the latest `compaction` item is disabled by default and only applies when `responsesApiAutoCompactInput` is `true`
|
|
383
385
|
- reasoning defaults for Anthropic -> Responses translation can be tuned with `modelReasoningEfforts`
|
|
386
|
+
- request parameters that a model rejects (e.g. `temperature`/`top_p` on reasoning models) are stripped on the Responses boundary rather than leaked upstream as a `400`; see [Responses Parameter Filters](#responses-parameter-filters)
|
|
384
387
|
- known unsupported builtin tools, such as `web_search`, fail explicitly with `400` instead of being silently removed
|
|
385
388
|
- external image URLs on the Responses path fail explicitly with `400`; use `file_id` or data URL image input instead
|
|
386
389
|
- official `input_file` and `item_reference` input items are modeled explicitly and validated before forwarding
|
|
@@ -399,6 +402,28 @@ Example opt-in configuration for these two Responses-specific policies:
|
|
|
399
402
|
|
|
400
403
|
> See [Responses Upstream Notes](./docs/responses-upstream-notes.md) for detailed upstream compatibility observations from live testing.
|
|
401
404
|
|
|
405
|
+
### Responses Parameter Filters
|
|
406
|
+
|
|
407
|
+
Some Copilot models reject request parameters that the OpenAI wire format allows. The clearest case: **reasoning models** (the `gpt-5` family, o-series, codex) reject sampling parameters and answer `POST /responses` with `400 Unsupported parameter: 'temperature' is not supported with this model.` Since the client cannot always be changed, the proxy strips the offending parameters on the Responses boundary instead of leaking the incompatibility outward.
|
|
408
|
+
|
|
409
|
+
This is expressed as a small rule engine that runs on both the native `/v1/responses` path and the `/v1/messages` → Responses translation path:
|
|
410
|
+
|
|
411
|
+
- **Built-in default rule:** any model that advertises `reasoning_effort` has `temperature` and `top_p` stripped. This covers the whole reasoning family (including future point releases like `gpt-5.4-mini`) with no configuration.
|
|
412
|
+
- **`responsesApiParameterFilters`:** add your own rules. Each rule is `{ "models": [glob, ...], "params": [name, ...] }`; every rule whose `models` glob matches the resolved model contributes its `params`. Rules are **added** to the default (the union of parameters is stripped). Model globs use the same `*` wildcard as `modelRewrites`.
|
|
413
|
+
- **`responsesApiParameterFiltersReplaceDefault`:** set to `true` to disable the built-in reasoning-model rule, so only your `responsesApiParameterFilters` apply — use this to fully **overwrite** the default behavior.
|
|
414
|
+
|
|
415
|
+
Stripped parameters are removed entirely (never sent as `null`), because upstream rejects the mere presence of the key.
|
|
416
|
+
|
|
417
|
+
```json
|
|
418
|
+
{
|
|
419
|
+
"responsesApiParameterFilters": [
|
|
420
|
+
{ "models": ["gpt-5*", "o1*"], "params": ["temperature", "top_p"] },
|
|
421
|
+
{ "models": ["some-model"], "params": ["top_k"] }
|
|
422
|
+
],
|
|
423
|
+
"responsesApiParameterFiltersReplaceDefault": false
|
|
424
|
+
}
|
|
425
|
+
```
|
|
426
|
+
|
|
402
427
|
## Docker
|
|
403
428
|
|
|
404
429
|
Pre-built images are available on GHCR:
|
package/dist/main.mjs
CHANGED
|
@@ -5754,6 +5754,11 @@ const configFileSchema = object({
|
|
|
5754
5754
|
responsesApiAutoCompactInput: boolean().optional(),
|
|
5755
5755
|
responsesApiAutoContextManagement: boolean().optional(),
|
|
5756
5756
|
responsesApiContextManagementModels: array(string()).optional(),
|
|
5757
|
+
responsesApiParameterFilters: array(object({
|
|
5758
|
+
models: array(string()).min(1),
|
|
5759
|
+
params: array(string()).min(1)
|
|
5760
|
+
})).optional(),
|
|
5761
|
+
responsesApiParameterFiltersReplaceDefault: boolean().optional(),
|
|
5757
5762
|
responsesOfficialEmulator: boolean().optional(),
|
|
5758
5763
|
responsesOfficialEmulatorTtlSeconds: number().int().positive().optional(),
|
|
5759
5764
|
modelReasoningEfforts: record(string(), reasoningEffortSchema).optional(),
|
|
@@ -5871,6 +5876,12 @@ var ConfigStore = class {
|
|
|
5871
5876
|
getReasoningEffort(model) {
|
|
5872
5877
|
return getCachedConfig().modelReasoningEfforts?.[model] ?? "high";
|
|
5873
5878
|
}
|
|
5879
|
+
getResponsesParameterFilters() {
|
|
5880
|
+
return getCachedConfig().responsesApiParameterFilters ?? [];
|
|
5881
|
+
}
|
|
5882
|
+
shouldReplaceDefaultParameterFilters() {
|
|
5883
|
+
return getCachedConfig().responsesApiParameterFiltersReplaceDefault ?? false;
|
|
5884
|
+
}
|
|
5874
5885
|
getModelRewrites() {
|
|
5875
5886
|
return getCachedConfig().modelRewrites ?? [];
|
|
5876
5887
|
}
|
|
@@ -5933,6 +5944,9 @@ var ModelCache = class {
|
|
|
5933
5944
|
supportsVision(model) {
|
|
5934
5945
|
return model?.capabilities.supports.vision ?? false;
|
|
5935
5946
|
}
|
|
5947
|
+
supportsReasoningEffort(model) {
|
|
5948
|
+
return (model?.capabilities.supports.reasoning_effort?.length ?? 0) > 0;
|
|
5949
|
+
}
|
|
5936
5950
|
supportsOutputConfig(model) {
|
|
5937
5951
|
if (!model) return true;
|
|
5938
5952
|
return !MODELS_REJECTING_OUTPUT_CONFIG.has(model.id);
|
|
@@ -7350,7 +7364,7 @@ const checkUsage = defineCommand({
|
|
|
7350
7364
|
});
|
|
7351
7365
|
//#endregion
|
|
7352
7366
|
//#region src/util/version.ts
|
|
7353
|
-
const VERSION = "0.7.
|
|
7367
|
+
const VERSION = "0.7.1";
|
|
7354
7368
|
//#endregion
|
|
7355
7369
|
//#region src/debug.ts
|
|
7356
7370
|
function getRuntimeInfo() {
|
|
@@ -50865,6 +50879,61 @@ function containsVisionContent(value) {
|
|
|
50865
50879
|
return false;
|
|
50866
50880
|
}
|
|
50867
50881
|
//#endregion
|
|
50882
|
+
//#region src/transform/parameter-filter.ts
|
|
50883
|
+
/**
|
|
50884
|
+
* Parameters the default rule strips for reasoning models on the Responses
|
|
50885
|
+
* boundary. Reasoning models (gpt-5 family, o-series, codex) reject sampling
|
|
50886
|
+
* parameters upstream with a 400 "Unsupported parameter" error, so the proxy
|
|
50887
|
+
* drops them instead of leaking the incompatibility to the client.
|
|
50888
|
+
*/
|
|
50889
|
+
const DEFAULT_REASONING_UNSUPPORTED_PARAMS = ["temperature", "top_p"];
|
|
50890
|
+
/**
|
|
50891
|
+
* A reasoning model is any model that advertises one or more
|
|
50892
|
+
* `reasoning_effort` levels. This dynamically covers the full reasoning
|
|
50893
|
+
* family (mini, codex, future point releases) without a hardcoded ID list.
|
|
50894
|
+
*/
|
|
50895
|
+
function isReasoningModel(model) {
|
|
50896
|
+
return modelCache.supportsReasoningEffort(model);
|
|
50897
|
+
}
|
|
50898
|
+
/**
|
|
50899
|
+
* Resolve the set of request parameters to strip for a given model on the
|
|
50900
|
+
* Responses boundary.
|
|
50901
|
+
*
|
|
50902
|
+
* Rule composition:
|
|
50903
|
+
* 1. Built-in default: reasoning models strip {@link DEFAULT_REASONING_UNSUPPORTED_PARAMS}.
|
|
50904
|
+
* Disabled entirely when `responsesApiParameterFiltersReplaceDefault` is true.
|
|
50905
|
+
* 2. User rules (`responsesApiParameterFilters`): every rule whose `models`
|
|
50906
|
+
* glob matches the resolved model id contributes its `params`.
|
|
50907
|
+
*
|
|
50908
|
+
* The result is the union of all matching rules, so user rules ADD to the
|
|
50909
|
+
* default. Setting `responsesApiParameterFiltersReplaceDefault: true` disables
|
|
50910
|
+
* the default so user rules fully OVERWRITE it.
|
|
50911
|
+
*/
|
|
50912
|
+
function resolveStrippedResponsesParams(model) {
|
|
50913
|
+
const params = /* @__PURE__ */ new Set();
|
|
50914
|
+
if (!configStore.shouldReplaceDefaultParameterFilters() && isReasoningModel(model)) for (const param of DEFAULT_REASONING_UNSUPPORTED_PARAMS) params.add(param);
|
|
50915
|
+
const modelId = model?.id;
|
|
50916
|
+
if (modelId) {
|
|
50917
|
+
for (const rule of configStore.getResponsesParameterFilters()) if (rule.models.some((pattern) => matchesGlob(pattern, modelId))) for (const param of rule.params) params.add(param);
|
|
50918
|
+
}
|
|
50919
|
+
return params;
|
|
50920
|
+
}
|
|
50921
|
+
/**
|
|
50922
|
+
* Strip unsupported parameters from a Responses payload before dispatch.
|
|
50923
|
+
* Keys are deleted entirely (never set to null) because upstream rejects the
|
|
50924
|
+
* mere presence of the key, not just non-null values.
|
|
50925
|
+
*/
|
|
50926
|
+
function applyResponsesParameterFilters(payload, model) {
|
|
50927
|
+
const strip = resolveStrippedResponsesParams(model);
|
|
50928
|
+
if (strip.size === 0) return;
|
|
50929
|
+
const removed = [];
|
|
50930
|
+
for (const key of strip) if (key in payload) {
|
|
50931
|
+
delete payload[key];
|
|
50932
|
+
removed.push(key);
|
|
50933
|
+
}
|
|
50934
|
+
if (removed.length > 0) consola.debug(`Stripped unsupported responses params for model ${model?.id}: ${removed.join(", ")}`);
|
|
50935
|
+
}
|
|
50936
|
+
//#endregion
|
|
50868
50937
|
//#region src/translator/responses/function-schema.ts
|
|
50869
50938
|
function isRecord$2(value) {
|
|
50870
50939
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
@@ -52020,6 +52089,7 @@ const responsesApiEntry = {
|
|
|
52020
52089
|
const responsesPayload = withTranslationErrors(() => translateAnthropicToResponsesPayload(ctx.anthropicPayload, { reasoningEffortResolver: (model) => configStore.getReasoningEffort(model) }));
|
|
52021
52090
|
applyContextManagement(responsesPayload, ctx.selectedModel?.capabilities.limits.max_prompt_tokens);
|
|
52022
52091
|
compactInputByLatestCompaction(responsesPayload);
|
|
52092
|
+
applyResponsesParameterFilters(responsesPayload, ctx.selectedModel);
|
|
52023
52093
|
const { vision, initiator } = getResponsesRequestOptions(responsesPayload);
|
|
52024
52094
|
return await runStrategy(createMessagesViaResponsesStrategy(ctx.copilotClient, responsesPayload, {
|
|
52025
52095
|
vision,
|
|
@@ -52517,6 +52587,7 @@ async function handleResponsesCore({ body, signal, headers }) {
|
|
|
52517
52587
|
if (!selectedModel) throwInvalidRequestError("The selected model could not be resolved.", "model");
|
|
52518
52588
|
if (!modelCache.supportsEndpoint(selectedModel, "/responses")) throwInvalidRequestError("The selected model does not support the responses endpoint.", "model");
|
|
52519
52589
|
applyContextManagement(payload, selectedModel.capabilities.limits.max_prompt_tokens);
|
|
52590
|
+
applyResponsesParameterFilters(payload, selectedModel);
|
|
52520
52591
|
},
|
|
52521
52592
|
buildStrategyContext({ payload, meta, copilotClient, upstreamSignal }) {
|
|
52522
52593
|
const { vision, initiator } = getResponsesRequestOptions(payload);
|