dsh-better-copilot 0.1.1 → 0.3.0
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/CHANGELOG.md +30 -0
- package/README.md +68 -3
- package/dist/auto-review.d.ts +24 -0
- package/dist/auto-review.js +57 -0
- package/dist/catalog.js +2 -1
- package/dist/client.js +98 -0
- package/dist/config.d.ts +22 -0
- package/dist/config.js +7 -0
- package/dist/index.d.ts +4 -1
- package/dist/index.js +2 -0
- package/dist/plugin.js +18 -5
- package/dist/temperature.d.ts +19 -0
- package/dist/temperature.js +39 -0
- package/package.json +16 -5
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,35 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.3.0 — builtin review routing and temperature compatibility
|
|
4
|
+
|
|
5
|
+
- Add volatile `autoReview.enabled`, `autoReview.model` and
|
|
6
|
+
`autoReview.reasoningEffort` settings. Builtin GitHub Copilot reviews default to
|
|
7
|
+
`gpt-6.1-terra`; DeepSeek Flash and non-Copilot routes retain their current
|
|
8
|
+
model. Omitted effort uses the selected model's adapter default.
|
|
9
|
+
- Redispatch detached frozen auxiliary requests through the public LLM
|
|
10
|
+
waterfall, leaving conversation requests, review policy, permission state,
|
|
11
|
+
Session metadata and cancellation unchanged; dispose the listener on unload.
|
|
12
|
+
- Remove the builtin Copilot reviewer's sampling parameter unless its model
|
|
13
|
+
positively advertises support. Map boolean account temperature capability
|
|
14
|
+
metadata into known/new catalog descriptors and strip explicitly unsupported
|
|
15
|
+
temperature on ordinary Copilot inference requests without model-name guesses.
|
|
16
|
+
- Compose temperature and Gemini normalization in the existing wire transform;
|
|
17
|
+
a Gemini guard failure cannot discard a temperature fix. Add routing, effort
|
|
18
|
+
validation, metadata, protocol, lifecycle and publication regressions.
|
|
19
|
+
|
|
20
|
+
## 0.2.0 — read-only subagent route badge
|
|
21
|
+
|
|
22
|
+
- Add a browser client half that shows an addressed subagent's effective model
|
|
23
|
+
and reasoning effort in the Web session header, beside the title. The label is
|
|
24
|
+
text only: no selector, no click target, no Host write, because the child's
|
|
25
|
+
route is fixed at creation and reused on resume.
|
|
26
|
+
- Read the route from the existing `modelSelection` session projection and show
|
|
27
|
+
an adapter-defaulted effort as `default`. Provider/model/effort ids render
|
|
28
|
+
verbatim; catalog display names are not available to client plugins.
|
|
29
|
+
- Keep ordinary conversations and the DSH-rendered subagent catalog untouched.
|
|
30
|
+
Ship the bundle through `./client` and a `dsh.client` declaration, with render
|
|
31
|
+
tests and the publication allowlist updated.
|
|
32
|
+
|
|
3
33
|
## 0.1.1 — DSH 0.2.2-rc.1 compatibility
|
|
4
34
|
|
|
5
35
|
- Retarget the manifest, peer ranges and development pins to DSH `0.2.2-rc.1`
|
package/README.md
CHANGED
|
@@ -12,6 +12,10 @@ One DeepSeek Harness plugin for GitHub Copilot:
|
|
|
12
12
|
provider for the ordinary `web_search` tool.
|
|
13
13
|
- **Gemini tool-declaration guard** — adds a missing root `properties` bucket to
|
|
14
14
|
tool parameter schemas for configured Copilot routes.
|
|
15
|
+
- **Builtin Auto review overrides** — selects the review model and effort without
|
|
16
|
+
changing the conversation model or the builtin authorization policy.
|
|
17
|
+
- **Temperature compatibility** — omits unsupported sampling controls on the
|
|
18
|
+
Copilot wire, including the builtin reviewer's hard-coded zero.
|
|
15
19
|
|
|
16
20
|
The plugin is a replacement for, and superset of, the earlier separate
|
|
17
21
|
`dsh-copilot-catalog`, `dsh-copilot-search` and `dsh-copilot-tool-schema`
|
|
@@ -67,14 +71,55 @@ The bundle patch:
|
|
|
67
71
|
providers: [github-copilot]
|
|
68
72
|
families: [gemini]
|
|
69
73
|
logFixes: false
|
|
74
|
+
autoReview:
|
|
75
|
+
enabled: true
|
|
76
|
+
# Optional: leave unset for Terra on Copilot, current model otherwise
|
|
77
|
+
# model: gpt-6.1-terra
|
|
78
|
+
# reasoningEffort: high
|
|
70
79
|
```
|
|
71
80
|
|
|
72
81
|
`providers` is the pi-ai adapter's own schema, reused unchanged, so every
|
|
73
82
|
provider setting behaves exactly as it does with the stock adapter. `search`
|
|
74
|
-
configures the web-search provider; `toolSchema` configures the Gemini guard
|
|
83
|
+
configures the web-search provider; `toolSchema` configures the Gemini guard;
|
|
84
|
+
`autoReview` configures the builtin review request only.
|
|
75
85
|
|
|
76
86
|
Loader patches replace a row's `config` wholesale, not key by key: an override
|
|
77
|
-
must restate
|
|
87
|
+
must restate every non-default group it needs together. Omitted `search`,
|
|
88
|
+
`toolSchema` and `autoReview` groups resolve their defaults.
|
|
89
|
+
|
|
90
|
+
## Builtin Auto review
|
|
91
|
+
|
|
92
|
+
`autoReview` applies only to requests marked `purpose: auto-review` by the builtin
|
|
93
|
+
experimental Auto review plugin. It does not enable Auto review, select a
|
|
94
|
+
permission preset, replace its policy or change normal conversation/subagent
|
|
95
|
+
requests.
|
|
96
|
+
|
|
97
|
+
- With no `autoReview.model`, GitHub Copilot reviews use `gpt-6.1-terra`.
|
|
98
|
+
DeepSeek Flash (`deepseek-v4-flash`) stays on Flash; other non-Copilot providers
|
|
99
|
+
retain their current model.
|
|
100
|
+
- `autoReview.model` selects an exact model id on the current provider;
|
|
101
|
+
`autoReview.reasoningEffort` selects that model's exact effort id. Explicit
|
|
102
|
+
settings win over defaults, including on DeepSeek. Omitted effort uses the
|
|
103
|
+
selected model's adapter default, not the main agent's effort. A model change
|
|
104
|
+
drops any prior model's effort. Unsupported explicit efforts fail normally
|
|
105
|
+
before provider I/O, without clamping or silently switching models.
|
|
106
|
+
- Settings are volatile: edits apply to the next review, not an in-flight review.
|
|
107
|
+
`autoReview.enabled: false` restores the builtin request's model and effort.
|
|
108
|
+
|
|
109
|
+
The builtin reviewer hard-codes `temperature: 0`. Copilot review requests omit
|
|
110
|
+
this parameter unless the selected catalog model positively advertises
|
|
111
|
+
`compat.supportsTemperature: true`, so older GPT metadata lacking a temperature
|
|
112
|
+
flag does not cause an unsupported-parameter error. This conservative rule is
|
|
113
|
+
limited to the reviewer-owned control; it does not discard explicit sampling
|
|
114
|
+
settings on ordinary requests with unknown support.
|
|
115
|
+
|
|
116
|
+
For ordinary Copilot inference, account discovery maps boolean
|
|
117
|
+
`capabilities.supports.temperature` into `compat.supportsTemperature`. The
|
|
118
|
+
existing trusted wire transform strips `temperature` only for exact catalog
|
|
119
|
+
models declaring `false`; supported and unknown metadata preserve the original
|
|
120
|
+
body. No model-name or reasoning-capability heuristics are used. Temperature
|
|
121
|
+
normalization remains active with wire identity disabled, and a Gemini guard
|
|
122
|
+
failure cannot undo a successful temperature fix.
|
|
78
123
|
|
|
79
124
|
## Wire identity
|
|
80
125
|
|
|
@@ -99,7 +144,27 @@ dispatches. Inside the same refcounted, trusted-path-checked interceptor that
|
|
|
99
144
|
carries the wire identity, it adds an empty root `properties` bucket to object
|
|
100
145
|
tool schemas that lack one for a configured Copilot route and model family, and
|
|
101
146
|
leaves every other byte of the request untouched. It never replaces an adapter or
|
|
102
|
-
another plugin's settings, and a guard failure
|
|
147
|
+
another plugin's settings, and a guard failure preserves the body after any
|
|
148
|
+
successful temperature normalization.
|
|
149
|
+
|
|
150
|
+
## Subagent model badge (Web)
|
|
151
|
+
|
|
152
|
+
The package now carries a small browser client half. When a subagent
|
|
153
|
+
conversation is open in the Web GUI, the session header shows the child's
|
|
154
|
+
effective route as muted text next to the title — for example
|
|
155
|
+
`gpt-6.1-sol · high`. The label is read-only: the plugin renders no selector and
|
|
156
|
+
writes nothing back, because the Host fixes a child's provider, model and
|
|
157
|
+
reasoning effort when the subagent is created and reuses the recorded route on
|
|
158
|
+
resume. An adapter-defaulted effort appears as `default`, and the tooltip and
|
|
159
|
+
accessible name say that the route is fixed.
|
|
160
|
+
|
|
161
|
+
The badge reads the child Session's `modelSelection` projection; it adds no Host
|
|
162
|
+
service, configuration field or RPC. Ids are shown exactly as the Host records
|
|
163
|
+
them, because catalog display names are not exposed to third-party client
|
|
164
|
+
plugins. Ordinary conversations are unchanged, and the subagent catalog
|
|
165
|
+
dropdown is untouched: DSH renders those rows in its own client package rather
|
|
166
|
+
than through an extension slot. The browser half is published as `./client`
|
|
167
|
+
and declared through `dsh.client`; no additional wiring is needed.
|
|
103
168
|
|
|
104
169
|
## Migration from the three predecessor packages
|
|
105
170
|
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { type GenerateOptions } from '@deepseek-ai/dsh-llm';
|
|
2
|
+
import { type ModelCatalog, type PluginContext } from './catalog-types.js';
|
|
3
|
+
import type { AutoReviewSettings } from './config.js';
|
|
4
|
+
/** GitHub Copilot's default builtin authorization reviewer. */
|
|
5
|
+
export declare const DEFAULT_REVIEW_MODEL = "gpt-6.1-terra";
|
|
6
|
+
/** One live configuration snapshot for a review request. */
|
|
7
|
+
export interface AutoReviewOverrides {
|
|
8
|
+
enabled: boolean;
|
|
9
|
+
model?: string;
|
|
10
|
+
reasoningEffort?: string;
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Select only an auxiliary builtin review route. Never modify loop requests or
|
|
14
|
+
* caller-owned inputs, including the frozen request the builtin reviewer sends.
|
|
15
|
+
* Changing model drops a previous model's effort unless explicitly overridden.
|
|
16
|
+
*/
|
|
17
|
+
export declare function configureAutoReview(options: GenerateOptions<string>, settings: AutoReviewOverrides, catalog?: ModelCatalog): GenerateOptions<string>;
|
|
18
|
+
/**
|
|
19
|
+
* The waterfall's next() accepts no replacement request. Redispatch a detached
|
|
20
|
+
* auxiliary request through the public LLM service instead, marking that exact
|
|
21
|
+
* object so the nested waterfall delegates once without rereading live settings.
|
|
22
|
+
* Cordis owns listener disposal; no review policy, permission or Session changes.
|
|
23
|
+
*/
|
|
24
|
+
export declare function installAutoReviewRouting(ctx: PluginContext, config: AutoReviewSettings, catalog: ModelCatalog): void;
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { isAgentLoopRequest } from '@deepseek-ai/dsh-llm';
|
|
2
|
+
import { asRecord } from './catalog-types.js';
|
|
3
|
+
/** GitHub Copilot's default builtin authorization reviewer. */
|
|
4
|
+
export const DEFAULT_REVIEW_MODEL = 'gpt-6.1-terra';
|
|
5
|
+
/**
|
|
6
|
+
* Select only an auxiliary builtin review route. Never modify loop requests or
|
|
7
|
+
* caller-owned inputs, including the frozen request the builtin reviewer sends.
|
|
8
|
+
* Changing model drops a previous model's effort unless explicitly overridden.
|
|
9
|
+
*/
|
|
10
|
+
export function configureAutoReview(options, settings, catalog = {}) {
|
|
11
|
+
if (!settings.enabled || options.purpose !== 'auto-review' || isAgentLoopRequest(options))
|
|
12
|
+
return options;
|
|
13
|
+
const model = settings.model ?? (options.provider === 'github-copilot' && options.model !== 'deepseek-v4-flash'
|
|
14
|
+
? DEFAULT_REVIEW_MODEL : options.model);
|
|
15
|
+
const reasoningEffort = settings.reasoningEffort ?? (model === options.model ? options.reasoningEffort : undefined);
|
|
16
|
+
const descriptor = Object.hasOwn(catalog, model) ? catalog[model] : undefined;
|
|
17
|
+
// The builtin's hard-coded zero is not a user inference setting. Require
|
|
18
|
+
// positive support for reviewer sampling; old SDK GPT catalogs omit this flag.
|
|
19
|
+
const omitTemperature = options.provider === 'github-copilot' && options.temperature !== undefined
|
|
20
|
+
&& !(descriptor?.provider === 'github-copilot' && asRecord(descriptor.compat)?.supportsTemperature === true);
|
|
21
|
+
if (model === options.model && reasoningEffort === options.reasoningEffort && !omitTemperature)
|
|
22
|
+
return options;
|
|
23
|
+
const { reasoningEffort: _previous, temperature, ...rest } = options;
|
|
24
|
+
return Object.freeze({
|
|
25
|
+
...rest, model,
|
|
26
|
+
...(reasoningEffort === undefined ? {} : { reasoningEffort: reasoningEffort }),
|
|
27
|
+
...(omitTemperature || temperature === undefined ? {} : { temperature }),
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* The waterfall's next() accepts no replacement request. Redispatch a detached
|
|
32
|
+
* auxiliary request through the public LLM service instead, marking that exact
|
|
33
|
+
* object so the nested waterfall delegates once without rereading live settings.
|
|
34
|
+
* Cordis owns listener disposal; no review policy, permission or Session changes.
|
|
35
|
+
*/
|
|
36
|
+
export function installAutoReviewRouting(ctx, config, catalog) {
|
|
37
|
+
const routed = new WeakSet();
|
|
38
|
+
ctx.on('llm/stream', function (options, next) {
|
|
39
|
+
if (routed.has(options) || options.purpose !== 'auto-review' || isAgentLoopRequest(options))
|
|
40
|
+
return next();
|
|
41
|
+
const enabled = config.enabled.get();
|
|
42
|
+
if (!enabled)
|
|
43
|
+
return next();
|
|
44
|
+
const model = config.model.get();
|
|
45
|
+
const reasoningEffort = config.reasoningEffort.get();
|
|
46
|
+
const selected = configureAutoReview(options, {
|
|
47
|
+
enabled,
|
|
48
|
+
...(model === undefined ? {} : { model }),
|
|
49
|
+
...(reasoningEffort === undefined ? {} : { reasoningEffort }),
|
|
50
|
+
}, catalog);
|
|
51
|
+
if (selected === options)
|
|
52
|
+
return next();
|
|
53
|
+
routed.add(selected);
|
|
54
|
+
// Purpose typing is owned by the optional builtin package, not a hard peer.
|
|
55
|
+
return this.stream(selected);
|
|
56
|
+
});
|
|
57
|
+
}
|
package/dist/catalog.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { asRecord } from './catalog-types.js';
|
|
2
|
+
import { withCopilotTemperatureSupport } from './temperature.js';
|
|
2
3
|
export const DEFAULT_COPILOT_BASE_URL = 'https://api.individual.githubcopilot.com';
|
|
3
4
|
// Provider identity defaults match the supported pi-ai Copilot OAuth client.
|
|
4
5
|
// These are protocol defaults, not metadata borrowed from a particular model.
|
|
@@ -165,7 +166,7 @@ export function selectAccountModels(response, catalog, { baseUrl = DEFAULT_COPIL
|
|
|
165
166
|
// Own-property lookup prevents Object.prototype members becoming descriptors.
|
|
166
167
|
const model = own(catalog, item.id) ?? createNewModel({ ...item, id: item.id }, headers, baseUrl);
|
|
167
168
|
if (model) {
|
|
168
|
-
models[item.id] = model;
|
|
169
|
+
models[item.id] = withCopilotTemperatureSupport(model, supports?.temperature);
|
|
169
170
|
unsupported.delete(item.id);
|
|
170
171
|
}
|
|
171
172
|
else {
|
package/dist/client.js
ADDED
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/// <reference path="./client.types.d.ts" />
|
|
3
|
+
/* Read-only subagent route badge for the Web conversation header.
|
|
4
|
+
* The Host fixes a subagent's provider, model and reasoning effort when the
|
|
5
|
+
* child is created, so this client half only presents them. It never renders a
|
|
6
|
+
* selection control and writes nothing back to the Session.
|
|
7
|
+
*/
|
|
8
|
+
window.__ModuleLoader__.load({
|
|
9
|
+
id: 'dsh-better-copilot',
|
|
10
|
+
factory(require) {
|
|
11
|
+
const React = require('react');
|
|
12
|
+
const h = React.createElement;
|
|
13
|
+
const NS = 'better-copilot.subagent';
|
|
14
|
+
const SLOT = 'conversation.session.header.actions';
|
|
15
|
+
const ID = 'better-copilot-subagent-model';
|
|
16
|
+
const en = {
|
|
17
|
+
'badge.defaultEffort': 'default',
|
|
18
|
+
'badge.fixed': "Model and effort are fixed for this subagent's lifetime.",
|
|
19
|
+
'badge.aria': 'Subagent model {model} from {provider}, reasoning effort {effort}. {fixed}',
|
|
20
|
+
};
|
|
21
|
+
const zh = {
|
|
22
|
+
'badge.defaultEffort': '默认',
|
|
23
|
+
'badge.fixed': '该子智能体的模型与推理强度在创建时已固定。',
|
|
24
|
+
'badge.aria': '子智能体模型 {model}({provider}),推理强度 {effort}。{fixed}',
|
|
25
|
+
};
|
|
26
|
+
/** Validate one projected selection; anything malformed is not rendered. */
|
|
27
|
+
function readSelection(value) {
|
|
28
|
+
if (typeof value !== 'object' || value === null)
|
|
29
|
+
return null;
|
|
30
|
+
const candidate = value;
|
|
31
|
+
const provider = typeof candidate.provider === 'string' ? candidate.provider : '';
|
|
32
|
+
const model = typeof candidate.model === 'string' ? candidate.model : '';
|
|
33
|
+
if (provider === '' || model === '')
|
|
34
|
+
return null;
|
|
35
|
+
const reasoningEffort = typeof candidate.reasoningEffort === 'string' && candidate.reasoningEffort !== ''
|
|
36
|
+
? candidate.reasoningEffort
|
|
37
|
+
: undefined;
|
|
38
|
+
return reasoningEffort === undefined ? { provider, model } : { provider, model, reasoningEffort };
|
|
39
|
+
}
|
|
40
|
+
/** Read the effective selection from the modelSelection wire view. */
|
|
41
|
+
function selectedOf(view) {
|
|
42
|
+
if (typeof view !== 'object' || view === null)
|
|
43
|
+
return null;
|
|
44
|
+
const projected = view;
|
|
45
|
+
return readSelection(projected.next) ?? readSelection(projected.lastUsed);
|
|
46
|
+
}
|
|
47
|
+
/** Compose the visible label, tooltip and accessible name for one route. */
|
|
48
|
+
function subagentBadge(selection, t) {
|
|
49
|
+
const explicit = selection.reasoningEffort;
|
|
50
|
+
const effort = explicit === undefined ? t('badge.defaultEffort') : explicit;
|
|
51
|
+
const route = explicit === undefined
|
|
52
|
+
? `${selection.provider}/${selection.model}`
|
|
53
|
+
: `${selection.provider}/${selection.model} · ${explicit}`;
|
|
54
|
+
return {
|
|
55
|
+
label: `${selection.model} · ${effort}`,
|
|
56
|
+
title: `${route} — ${t('badge.fixed')}`,
|
|
57
|
+
aria: t('badge.aria', {
|
|
58
|
+
provider: selection.provider,
|
|
59
|
+
model: selection.model,
|
|
60
|
+
effort,
|
|
61
|
+
fixed: t('badge.fixed'),
|
|
62
|
+
}),
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
const badgeStyle = {
|
|
66
|
+
display: 'inline-block',
|
|
67
|
+
maxWidth: '26ch',
|
|
68
|
+
overflow: 'hidden',
|
|
69
|
+
color: 'var(--dsw-alias-label-tertiary)',
|
|
70
|
+
fontSize: '12px',
|
|
71
|
+
lineHeight: '18px',
|
|
72
|
+
whiteSpace: 'nowrap',
|
|
73
|
+
textOverflow: 'ellipsis',
|
|
74
|
+
verticalAlign: 'middle',
|
|
75
|
+
cursor: 'default',
|
|
76
|
+
};
|
|
77
|
+
/** Read-only header cell: visible only for an addressed subagent's own route. */
|
|
78
|
+
function SubagentModelBadge({ sessionId, useSessions, useProjection, t }) {
|
|
79
|
+
const subagent = useSessions((state) => state.byId[sessionId]?.origin === 'subagent');
|
|
80
|
+
const view = useProjection('modelSelection');
|
|
81
|
+
if (!subagent)
|
|
82
|
+
return null;
|
|
83
|
+
const selection = selectedOf(view);
|
|
84
|
+
if (selection === null)
|
|
85
|
+
return null;
|
|
86
|
+
const badge = subagentBadge(selection, t);
|
|
87
|
+
return h('span', { style: badgeStyle, title: badge.title, 'aria-label': badge.aria }, badge.label);
|
|
88
|
+
}
|
|
89
|
+
function apply(ctx) {
|
|
90
|
+
ctx.effect(() => ctx.locale.register(NS, { en, zh }));
|
|
91
|
+
ctx.slots.inject(SLOT, () => ctx.slots.register({ name: SLOT, id: ID, order: 15, locale: NS }, SubagentModelBadge));
|
|
92
|
+
}
|
|
93
|
+
return {
|
|
94
|
+
inject: ['slots', 'locale'],
|
|
95
|
+
apply,
|
|
96
|
+
};
|
|
97
|
+
},
|
|
98
|
+
});
|
package/dist/config.d.ts
CHANGED
|
@@ -30,11 +30,18 @@ export interface GuardConfigValue {
|
|
|
30
30
|
families: Volatile<string[]>;
|
|
31
31
|
logFixes: Volatile<boolean>;
|
|
32
32
|
}
|
|
33
|
+
/** Builtin Auto review routing; omitted leaves retain the model's adapter defaults. */
|
|
34
|
+
export interface AutoReviewSettings {
|
|
35
|
+
enabled: Volatile<boolean>;
|
|
36
|
+
model: Volatile<string | undefined>;
|
|
37
|
+
reasoningEffort: Volatile<string | undefined>;
|
|
38
|
+
}
|
|
33
39
|
/** The one configuration object every capability reads from. */
|
|
34
40
|
export interface BetterCopilotConfig {
|
|
35
41
|
providers: ProvidersValue;
|
|
36
42
|
search: SearchSettings;
|
|
37
43
|
toolSchema: GuardConfigValue;
|
|
44
|
+
autoReview: AutoReviewSettings;
|
|
38
45
|
}
|
|
39
46
|
/** User-facing configuration: plain values, before volatile resolution. */
|
|
40
47
|
export interface BetterCopilotConfigInput {
|
|
@@ -58,6 +65,11 @@ export interface BetterCopilotConfigInput {
|
|
|
58
65
|
families?: string[];
|
|
59
66
|
logFixes?: boolean;
|
|
60
67
|
};
|
|
68
|
+
autoReview?: {
|
|
69
|
+
enabled?: boolean;
|
|
70
|
+
model?: string;
|
|
71
|
+
reasoningEffort?: string;
|
|
72
|
+
};
|
|
61
73
|
}
|
|
62
74
|
/**
|
|
63
75
|
* The merged Config schema as the plugin and the Loader see it: callable, with a
|
|
@@ -115,6 +127,16 @@ export declare const GuardConfig: z<Schemastery.ObjectS<NoInfer<{
|
|
|
115
127
|
families: z<NoInfer<string[]>, NoInfer<string[]>, "volatile-defined">;
|
|
116
128
|
logFixes: z<boolean, boolean, "volatile-defined">;
|
|
117
129
|
}>>, "plain">;
|
|
130
|
+
/** Only the builtin reviewer is affected; exact model/effort ids are validated by LLM dispatch. */
|
|
131
|
+
export declare const AutoReviewConfig: z<Schemastery.ObjectS<NoInfer<{
|
|
132
|
+
enabled: z<boolean, boolean, "volatile-defined">;
|
|
133
|
+
model: z<string, string, "volatile">;
|
|
134
|
+
reasoningEffort: z<string, string, "volatile">;
|
|
135
|
+
}>>, Schemastery.ObjectT<NoInfer<{
|
|
136
|
+
enabled: z<boolean, boolean, "volatile-defined">;
|
|
137
|
+
model: z<string, string, "volatile">;
|
|
138
|
+
reasoningEffort: z<string, string, "volatile">;
|
|
139
|
+
}>>, "plain">;
|
|
118
140
|
/**
|
|
119
141
|
* The merged plugin's single configuration object: the adapter's provider map
|
|
120
142
|
* plus one group per additional capability.
|
package/dist/config.js
CHANGED
|
@@ -23,6 +23,12 @@ export const GuardConfig = z.object({
|
|
|
23
23
|
families: z.array(z.string()).default(['gemini']).volatile(),
|
|
24
24
|
logFixes: z.boolean().default(false).volatile(),
|
|
25
25
|
});
|
|
26
|
+
/** Only the builtin reviewer is affected; exact model/effort ids are validated by LLM dispatch. */
|
|
27
|
+
export const AutoReviewConfig = z.object({
|
|
28
|
+
enabled: z.boolean().default(true).volatile(),
|
|
29
|
+
model: z.string().min(1).volatile(),
|
|
30
|
+
reasoningEffort: z.string().min(1).volatile(),
|
|
31
|
+
});
|
|
26
32
|
/**
|
|
27
33
|
* Reuse the pi-ai adapter's own provider profile schema instead of restating it.
|
|
28
34
|
* A restated schema would silently drift from the adapter's defaults, validation
|
|
@@ -47,5 +53,6 @@ export function createConfig(originalConfig) {
|
|
|
47
53
|
providers: providerProfileSchema(originalConfig),
|
|
48
54
|
search: SearchConfig,
|
|
49
55
|
toolSchema: GuardConfig,
|
|
56
|
+
autoReview: AutoReviewConfig,
|
|
50
57
|
});
|
|
51
58
|
}
|
package/dist/index.d.ts
CHANGED
|
@@ -7,6 +7,9 @@ export declare const apply: (ctx: PluginContext, config: BetterCopilotConfig) =>
|
|
|
7
7
|
/** Preserved predecessor entry point: one read-only discovery with the merged runtime. */
|
|
8
8
|
export declare const syncCopilotCatalog: (credentials?: CredentialReader, authOptions?: CopilotAuthOptions) => Promise<SyncResult>;
|
|
9
9
|
export { DEFAULT_SEARCH_MODEL } from './config.js';
|
|
10
|
+
export { configureAutoReview, DEFAULT_REVIEW_MODEL } from './auto-review.js';
|
|
11
|
+
export type { AutoReviewOverrides } from './auto-review.js';
|
|
12
|
+
export { normalizeCopilotTemperature } from './temperature.js';
|
|
10
13
|
export { liveCost, selectAccountModels } from './catalog.js';
|
|
11
14
|
export { DiscoveryError } from './discovery.js';
|
|
12
15
|
export { createCopilotWireIdentity, installCopilotIdentityHeaders, installCopilotRequestTransform, isCopilotRequestPath, isTrustedCopilotUrl, rewriteCopilotRequestHeaders, wireIdentityMode, } from './identity.js';
|
|
@@ -16,7 +19,7 @@ export { COPILOT_SEARCH_PROVIDER_ID, CopilotSearchProvider } from './provider.js
|
|
|
16
19
|
export { matchesRoute, normalizeToolSchemas, TOOL_SCHEMA_SKIPPED } from './schema.js';
|
|
17
20
|
export { loadRuntime } from './runtime.js';
|
|
18
21
|
export type { BetterCopilotRuntime, RuntimeOptions } from './runtime.js';
|
|
19
|
-
export type { BetterCopilotConfig, BetterCopilotConfigSchema } from './config.js';
|
|
22
|
+
export type { AutoReviewSettings, BetterCopilotConfig, BetterCopilotConfigSchema } from './config.js';
|
|
20
23
|
export type { BetterCopilotDependencies, BetterCopilotPlugin } from './plugin.js';
|
|
21
24
|
export type { AccountModelOptions } from './catalog.js';
|
|
22
25
|
export type { NormalizeResult } from './schema.js';
|
package/dist/index.js
CHANGED
|
@@ -9,6 +9,8 @@ export const apply = (ctx, config) => plugin.apply(ctx, config);
|
|
|
9
9
|
/** Preserved predecessor entry point: one read-only discovery with the merged runtime. */
|
|
10
10
|
export const syncCopilotCatalog = (credentials, authOptions) => plugin.sync(credentials, authOptions);
|
|
11
11
|
export { DEFAULT_SEARCH_MODEL } from './config.js';
|
|
12
|
+
export { configureAutoReview, DEFAULT_REVIEW_MODEL } from './auto-review.js';
|
|
13
|
+
export { normalizeCopilotTemperature } from './temperature.js';
|
|
12
14
|
export { liveCost, selectAccountModels } from './catalog.js';
|
|
13
15
|
export { DiscoveryError } from './discovery.js';
|
|
14
16
|
export { createCopilotWireIdentity, installCopilotIdentityHeaders, installCopilotRequestTransform, isCopilotRequestPath, isTrustedCopilotUrl, rewriteCopilotRequestHeaders, wireIdentityMode, } from './identity.js';
|
package/dist/plugin.js
CHANGED
|
@@ -6,6 +6,8 @@ import { createCopilotWireIdentity, installCopilotIdentityHeaders, installCopilo
|
|
|
6
6
|
import { CopilotSearchProvider } from './provider.js';
|
|
7
7
|
import { TOOL_SCHEMA_SKIPPED } from './schema.js';
|
|
8
8
|
import { guardCopilotRequestBody } from './guard.js';
|
|
9
|
+
import { installAutoReviewRouting } from './auto-review.js';
|
|
10
|
+
import { normalizeCopilotTemperature } from './temperature.js';
|
|
9
11
|
export const name = 'better-copilot';
|
|
10
12
|
/** Search and guard are optional capabilities; the adapter requires these two. */
|
|
11
13
|
export const inject = ['llm', 'credentials'];
|
|
@@ -96,21 +98,28 @@ export function createPlugin(dependencies) {
|
|
|
96
98
|
// the defect. It is installed even when wire identity is opted out.
|
|
97
99
|
contain('tool-schema guard', 'Gemini tool-schema normalization', () => {
|
|
98
100
|
const transform = ({ body }) => {
|
|
101
|
+
let normalized;
|
|
102
|
+
try {
|
|
103
|
+
normalized = normalizeCopilotTemperature(body, dependencies.catalog);
|
|
104
|
+
}
|
|
105
|
+
catch {
|
|
106
|
+
log('warn', 'better-copilot: skipped temperature normalization after an internal error');
|
|
107
|
+
}
|
|
99
108
|
try {
|
|
100
109
|
const settings = guardSettings(config.toolSchema);
|
|
101
|
-
const result = guardCopilotRequestBody(body, settings);
|
|
110
|
+
const result = guardCopilotRequestBody(normalized ?? body, settings);
|
|
102
111
|
if (settings.logFixes) {
|
|
103
112
|
if (result.fixed.length > 0)
|
|
104
113
|
log('info', 'better-copilot: added a properties bucket to %d tool schema(s) on the Copilot wire', result.fixed.length);
|
|
105
114
|
if (result.skipped.length > 0)
|
|
106
115
|
log('warn', 'better-copilot: %s left %d tool schema(s) unchanged: %s', TOOL_SCHEMA_SKIPPED, result.skipped.length, JSON.stringify(result.skipped));
|
|
107
116
|
}
|
|
108
|
-
return result.body;
|
|
117
|
+
return result.body ?? normalized;
|
|
109
118
|
}
|
|
110
119
|
catch {
|
|
111
|
-
//
|
|
120
|
+
// Guard failure never fails a call or discards a temperature fix.
|
|
112
121
|
log('warn', 'better-copilot: skipped one request after an internal error');
|
|
113
|
-
return
|
|
122
|
+
return normalized;
|
|
114
123
|
}
|
|
115
124
|
};
|
|
116
125
|
ctx.effect(() => installCopilotRequestTransform(transform), 'copilot: tool-schema wire normalization');
|
|
@@ -144,7 +153,11 @@ export function createPlugin(dependencies) {
|
|
|
144
153
|
});
|
|
145
154
|
});
|
|
146
155
|
});
|
|
147
|
-
// 4.
|
|
156
|
+
// 4. Builtin authorization reviewer routing, independently contained.
|
|
157
|
+
contain('auto-review routing', 'builtin review model overrides', () => {
|
|
158
|
+
installAutoReviewRouting(ctx, config.autoReview, dependencies.catalog);
|
|
159
|
+
});
|
|
160
|
+
// 5. Catalog discovery, then the adapter itself. Only this step is fatal:
|
|
148
161
|
// without the adapter there is no Copilot inference route at all.
|
|
149
162
|
try {
|
|
150
163
|
const credentials = ctx.get('credentials');
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { Api, Model } from '@earendil-works/pi-ai';
|
|
2
|
+
import { type ModelCatalog } from './catalog-types.js';
|
|
3
|
+
/**
|
|
4
|
+
* Apply only an explicit boolean from capabilities.supports.temperature.
|
|
5
|
+
* OpenAI compat schemas do not yet declare this field, but the wire boundary
|
|
6
|
+
* reads the same metadata used natively by pi-ai's Anthropic serializer.
|
|
7
|
+
* Preserve descriptor identity when discovery has nothing new to say.
|
|
8
|
+
*/
|
|
9
|
+
export declare function withCopilotTemperatureSupport<T extends Model<Api>>(model: T, support: unknown): T;
|
|
10
|
+
/**
|
|
11
|
+
* Omit an unsupported temperature after SDK serialization, for every request
|
|
12
|
+
* purpose (including auxiliary review). IDs are opaque and reasoning capability
|
|
13
|
+
* says nothing about temperature: only an exact catalog descriptor with explicit
|
|
14
|
+
* false compatibility metadata is sufficient to remove the field.
|
|
15
|
+
*
|
|
16
|
+
* Install through the existing trusted Copilot request transform, not as a
|
|
17
|
+
* separate interceptor. Undefined means the original body stays byte-identical.
|
|
18
|
+
*/
|
|
19
|
+
export declare function normalizeCopilotTemperature(body: string, catalog: ModelCatalog): string | undefined;
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { asRecord } from './catalog-types.js';
|
|
2
|
+
/**
|
|
3
|
+
* Apply only an explicit boolean from capabilities.supports.temperature.
|
|
4
|
+
* OpenAI compat schemas do not yet declare this field, but the wire boundary
|
|
5
|
+
* reads the same metadata used natively by pi-ai's Anthropic serializer.
|
|
6
|
+
* Preserve descriptor identity when discovery has nothing new to say.
|
|
7
|
+
*/
|
|
8
|
+
export function withCopilotTemperatureSupport(model, support) {
|
|
9
|
+
if (typeof support !== 'boolean' || asRecord(model.compat)?.supportsTemperature === support)
|
|
10
|
+
return model;
|
|
11
|
+
return { ...model, compat: { ...model.compat, supportsTemperature: support } };
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Omit an unsupported temperature after SDK serialization, for every request
|
|
15
|
+
* purpose (including auxiliary review). IDs are opaque and reasoning capability
|
|
16
|
+
* says nothing about temperature: only an exact catalog descriptor with explicit
|
|
17
|
+
* false compatibility metadata is sufficient to remove the field.
|
|
18
|
+
*
|
|
19
|
+
* Install through the existing trusted Copilot request transform, not as a
|
|
20
|
+
* separate interceptor. Undefined means the original body stays byte-identical.
|
|
21
|
+
*/
|
|
22
|
+
export function normalizeCopilotTemperature(body, catalog) {
|
|
23
|
+
let parsed;
|
|
24
|
+
try {
|
|
25
|
+
parsed = JSON.parse(body);
|
|
26
|
+
}
|
|
27
|
+
catch {
|
|
28
|
+
return undefined;
|
|
29
|
+
}
|
|
30
|
+
const request = asRecord(parsed);
|
|
31
|
+
if (!request || typeof request.model !== 'string' || !Object.hasOwn(request, 'temperature') ||
|
|
32
|
+
!Object.hasOwn(catalog, request.model))
|
|
33
|
+
return undefined;
|
|
34
|
+
const model = catalog[request.model];
|
|
35
|
+
if (model?.provider !== 'github-copilot' || asRecord(model.compat)?.supportsTemperature !== false)
|
|
36
|
+
return undefined;
|
|
37
|
+
delete request.temperature;
|
|
38
|
+
return JSON.stringify(request);
|
|
39
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-better-copilot",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"description": "Account-aware GitHub Copilot model discovery, Copilot-compatible wire identity, native Responses web search and a Copilot Gemini tool-declaration guard in one DeepSeek Harness plugin.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -30,6 +30,7 @@
|
|
|
30
30
|
"types": "./dist/guard.d.ts",
|
|
31
31
|
"import": "./dist/guard.js"
|
|
32
32
|
},
|
|
33
|
+
"./client": "./dist/client.js",
|
|
33
34
|
"./package.json": "./package.json"
|
|
34
35
|
},
|
|
35
36
|
"files": [
|
|
@@ -72,6 +73,7 @@
|
|
|
72
73
|
"@deepseek-ai/cordis": "~4.1.0",
|
|
73
74
|
"@deepseek-ai/dsh-credentials": "0.2.2-rc.1",
|
|
74
75
|
"@deepseek-ai/dsh-launch-environment": "0.2.2-rc.1",
|
|
76
|
+
"@deepseek-ai/dsh-llm": "0.2.2-rc.1",
|
|
75
77
|
"@deepseek-ai/dsh-llm-pi-ai": "0.2.2-rc.1",
|
|
76
78
|
"@deepseek-ai/dsh-system-prompt": "0.2.2-rc.1",
|
|
77
79
|
"@deepseek-ai/dsh-web": "0.2.2-rc.1",
|
|
@@ -83,24 +85,33 @@
|
|
|
83
85
|
"@deepseek-ai/dsh-agent": "0.2.2-rc.1",
|
|
84
86
|
"@deepseek-ai/dsh-credentials": "0.2.2-rc.1",
|
|
85
87
|
"@deepseek-ai/dsh-launch-environment": "0.2.2-rc.1",
|
|
88
|
+
"@deepseek-ai/dsh-llm": "0.2.2-rc.1",
|
|
86
89
|
"@deepseek-ai/dsh-llm-pi-ai": "0.2.2-rc.1",
|
|
87
90
|
"@deepseek-ai/dsh-scope": "0.2.2-rc.1",
|
|
88
91
|
"@deepseek-ai/dsh-session": "0.2.2-rc.1",
|
|
89
92
|
"@deepseek-ai/dsh-system-prompt": "0.2.2-rc.1",
|
|
90
93
|
"@deepseek-ai/dsh-web": "0.2.2-rc.1",
|
|
91
94
|
"@deepseek-ai/schemastery": "3.19.0",
|
|
92
|
-
"@earendil-works/pi-ai": "1.3.0"
|
|
95
|
+
"@earendil-works/pi-ai": "1.3.0",
|
|
96
|
+
"@types/react": "^19.3.0",
|
|
97
|
+
"@types/react-dom": "^19.3.0",
|
|
98
|
+
"react": "^19.3.0",
|
|
99
|
+
"react-dom": "^19.3.0"
|
|
93
100
|
},
|
|
94
101
|
"dsh": {
|
|
95
102
|
"manifestVersion": 1,
|
|
96
103
|
"bundle": {
|
|
97
104
|
"patch": "./cordis.patch.yml"
|
|
105
|
+
},
|
|
106
|
+
"client": {
|
|
107
|
+
"platform": "web",
|
|
108
|
+
"immediately": true
|
|
98
109
|
}
|
|
99
110
|
},
|
|
100
111
|
"scripts": {
|
|
101
|
-
"build": "tsc -p tsconfig.json",
|
|
102
|
-
"typecheck": "tsc -p tsconfig.json --noEmit && tsc -p test/tsconfig.json",
|
|
103
|
-
"test": "pnpm run build && node --import tsx --test test/catalog.test.ts test/discovery.test.ts test/identity.test.ts test/secrets.test.ts test/catalog-plugin.test.ts test/config.test.ts test/composition.test.ts test/auth.test.ts test/refresh.test.ts test/provider.test.ts test/responses.test.ts test/search-plugin.test.ts test/schema.test.ts test/guard.test.ts test/guard-plugin.test.ts test/patch.test.ts",
|
|
112
|
+
"build": "tsc -p tsconfig.json && tsc -p tsconfig.client.json",
|
|
113
|
+
"typecheck": "tsc -p tsconfig.json --noEmit && tsc -p test/tsconfig.json && tsc -p tsconfig.client.json --noEmit",
|
|
114
|
+
"test": "pnpm run build && node --import tsx --test test/catalog.test.ts test/discovery.test.ts test/identity.test.ts test/secrets.test.ts test/catalog-plugin.test.ts test/config.test.ts test/composition.test.ts test/auth.test.ts test/refresh.test.ts test/provider.test.ts test/responses.test.ts test/search-plugin.test.ts test/schema.test.ts test/guard.test.ts test/guard-plugin.test.ts test/patch.test.ts test/client.render.test.ts test/temperature.test.ts test/auto-review.test.ts",
|
|
104
115
|
"test:integration": "pnpm run build && node --import tsx --test test/integration/nested-layout.test.ts test/integration/catalog-runtime.test.ts test/integration/enterprise.test.ts test/integration/identity-wire.test.ts test/integration/search-runtime.test.ts test/integration/guard-runtime.test.ts test/integration/wire.test.ts",
|
|
105
116
|
"test:types": "pnpm run build && tsc -p test/types/tsconfig.json",
|
|
106
117
|
"test:pack": "pnpm run build && tsx ../../scripts/check-pack.ts .",
|