@bitkyc08/opencodex 2.69.0 → 2.70.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/gui/dist/assets/App-_MrbTrBe.js +51 -0
- package/gui/dist/assets/{Tray-uqKRqfvw.js → Tray-5PuUalET.js} +1 -1
- package/gui/dist/assets/index-DKrO2eF3.js +86 -0
- package/gui/dist/assets/quota-summary-BCLivc0Q.js +1 -0
- package/gui/dist/index.html +1 -1
- package/package.json +1 -1
- package/src/adapters/anthropic-model-contract.ts +119 -0
- package/src/adapters/anthropic.ts +14 -83
- package/src/adapters/base.ts +2 -0
- package/src/adapters/command-code-project-context.ts +542 -0
- package/src/adapters/command-code-tool-text.ts +99 -67
- package/src/adapters/command-code.ts +5 -1
- package/src/adapters/cursor/catalog.ts +11 -0
- package/src/adapters/cursor/effort-map.ts +3 -0
- package/src/adapters/devin/cloud-direct/catalog.ts +91 -0
- package/src/adapters/devin/cloud-direct/chat.ts +78 -48
- package/src/adapters/devin/cloud-direct/tool-schema.ts +121 -0
- package/src/adapters/devin/context-overflow.ts +75 -0
- package/src/adapters/devin/live-models.ts +174 -13
- package/src/adapters/devin/reasoning-signature.ts +100 -0
- package/src/adapters/devin.ts +310 -117
- package/src/adapters/kiro/payload.ts +10 -10
- package/src/adapters/kiro-images.ts +58 -7
- package/src/adapters/openai-chat/passthrough.ts +3 -1
- package/src/adapters/openai-chat.ts +2 -1
- package/src/adapters/openai-responses/passthrough.ts +2 -1
- package/src/bridge/response-json.ts +15 -1
- package/src/bridge/sse.ts +3 -1
- package/src/claude/intercept/model-bindings.ts +1 -0
- package/src/cli/account-extended.ts +31 -1
- package/src/cli/account.ts +3 -0
- package/src/cli/capabilities.ts +13 -0
- package/src/cli/client-start-fence.ts +36 -0
- package/src/cli/dispatch.ts +3 -2
- package/src/cli/index.ts +98 -117
- package/src/cli/provider-runtime.ts +23 -2
- package/src/cli/provider.ts +3 -0
- package/src/cli/resolve.ts +18 -0
- package/src/cli/start-owner-fence.ts +30 -0
- package/src/cli/stop-approval.ts +13 -1
- package/src/cli/stop-restore.ts +73 -0
- package/src/cli/tray-proxy.ts +221 -18
- package/src/cli/version-skew.ts +25 -23
- package/src/client/runtime.ts +8 -4
- package/src/codex/auth-api/account-list.ts +11 -5
- package/src/codex/auth-api/login-flow.ts +5 -3
- package/src/codex/auth-api/main-account-probe.ts +186 -110
- package/src/codex/auth-api/pool-mode-gate.ts +61 -7
- package/src/codex/auth-api/pool-quota-probe.ts +142 -90
- package/src/codex/auth-api/reset-credit-service.ts +1 -1
- package/src/codex/catalog/gather-capture.ts +1 -0
- package/src/codex/catalog/model-hints.ts +3 -3
- package/src/codex/catalog/provider-models.ts +2 -0
- package/src/codex/low-quota-events.ts +28 -0
- package/src/codex/low-quota-observer.ts +23 -0
- package/src/codex/low-quota-protection.ts +272 -0
- package/src/codex/quota-query-backoff.ts +148 -0
- package/src/codex/quota.ts +7 -2
- package/src/config/diagnostics.ts +10 -1
- package/src/config/load-degrade.ts +17 -0
- package/src/config/mutation-lock.ts +3 -4
- package/src/config/process-state.ts +1 -1
- package/src/config/provider-validation.ts +22 -0
- package/src/config/schema/blocked-model-redirects.ts +17 -0
- package/src/config/schema/config-schema.ts +4 -1
- package/src/config/schema/leaf-validators.ts +33 -2
- package/src/config/serving-runtimes.ts +412 -0
- package/src/generated/compatibility-version.json +211 -115
- package/src/generated/model-metadata.ts +3 -3
- package/src/images/loop.ts +3 -0
- package/src/lib/errors.ts +2 -1
- package/src/lib/handled-signal-exit.ts +23 -0
- package/src/lib/request-failure-model.ts +1 -0
- package/src/lib/self-launch-argv.ts +9 -0
- package/src/lib/shadow-call.ts +5 -2
- package/src/lib/strict-semver.ts +26 -0
- package/src/lib/transient-service-path.ts +16 -0
- package/src/lib/windows-service-wrappers.ts +10 -6
- package/src/lib/winsw.ts +3 -1
- package/src/oauth/anthropic-model-routes.ts +105 -0
- package/src/oauth/anthropic-routing.ts +85 -48
- package/src/oauth/devin.ts +135 -5
- package/src/oauth/index.ts +20 -3
- package/src/oauth/kiro-terminal-failover.ts +23 -9
- package/src/oauth/pool-settings-capability.ts +10 -2
- package/src/oauth/store.ts +1 -1
- package/src/protocols/encoders/adapter-events.ts +3 -1
- package/src/providers/github-copilot-context.ts +27 -0
- package/src/providers/kiro-models.ts +4 -0
- package/src/providers/model-rename-fields.ts +3 -0
- package/src/providers/quota/account-cache.ts +8 -3
- package/src/providers/quota/devin.ts +286 -0
- package/src/providers/quota/vendor-probes-oauth.ts +6 -2
- package/src/providers/quota.ts +1 -1
- package/src/providers/registry/entries-core.ts +1 -1
- package/src/providers/registry/model-seeds.ts +5 -2
- package/src/responses/compaction-images.ts +27 -0
- package/src/router.ts +104 -16
- package/src/routing/analytics.ts +1 -0
- package/src/server/auth-cors.ts +9 -1
- package/src/server/background-lifecycle.ts +21 -10
- package/src/server/chat-completions.ts +2 -1
- package/src/server/chat-native.ts +1 -0
- package/src/server/index.ts +4 -8
- package/src/server/inference/client-encoder-delivery.ts +3 -0
- package/src/server/management/context.ts +3 -0
- package/src/server/management/low-quota-routes.ts +14 -0
- package/src/server/management/oauth-account-routes.ts +21 -1
- package/src/server/management/provider-overwrite-carry.ts +79 -6
- package/src/server/management/provider-routes.ts +41 -28
- package/src/server/management/route-registry.ts +1 -0
- package/src/server/management-api.ts +7 -0
- package/src/server/responses/adapter-continuation.ts +3 -0
- package/src/server/responses/adapter-delivery.ts +3 -0
- package/src/server/responses/adapter-dispatch.ts +20 -4
- package/src/server/responses/antigravity-validation-refusal.ts +20 -0
- package/src/server/responses/core-auth.ts +1 -0
- package/src/server/responses/core-codex-account.ts +1 -0
- package/src/server/responses/core-normalize.ts +4 -0
- package/src/server/responses/passthrough-dispatch.ts +8 -1
- package/src/server/responses/request-sidecar-auth.ts +2 -0
- package/src/server/responses/request-transport.ts +27 -10
- package/src/server/responses/response-effects.ts +4 -1
- package/src/server/responses/run-turn-execution.ts +126 -42
- package/src/server/responses/sidecar-execution.ts +4 -1
- package/src/service/diagnostics.ts +3 -1
- package/src/service/guarded-manager-target.ts +39 -12
- package/src/service/launchd.ts +14 -6
- package/src/service/repair.ts +6 -1
- package/src/service/service-child-ownership.ts +168 -0
- package/src/service/state.ts +22 -4
- package/src/service/systemd.ts +2 -2
- package/src/service/windows-guarded-manager.ts +530 -0
- package/src/service/windows-taskxml.ts +2 -2
- package/src/service.ts +2 -0
- package/src/types/config.ts +26 -0
- package/src/types/provider.ts +12 -0
- package/src/types/request.ts +2 -0
- package/src/types.ts +1 -0
- package/src/usage/expected-prices.ts +13 -0
- package/src/usage/telemetry-contract.ts +1 -0
- package/src/vision/anthropic-describe.ts +2 -1
- package/src/web-search/anthropic-executor.ts +2 -1
- package/src/web-search/loop.ts +1 -0
- package/gui/dist/assets/App-DDbgOTh6.js +0 -51
- package/gui/dist/assets/index-CuaBJkG4.js +0 -86
- package/gui/dist/assets/quota-summary-COppQSVq.js +0 -1
|
@@ -2,12 +2,12 @@
|
|
|
2
2
|
* Live Devin / Cognition model discovery via GetCascadeModelConfigs.
|
|
3
3
|
*
|
|
4
4
|
* The live catalog is the source of truth for the model roster. The endpoint
|
|
5
|
-
* returns
|
|
6
|
-
*
|
|
7
|
-
*
|
|
5
|
+
* returns one row per variant (e.g. `gpt-5-6-sol-high`), grouped into families;
|
|
6
|
+
* the picker shows one id per family and the adapter picks the family member
|
|
7
|
+
* for the requested effort at request time. `DEVIN_STATIC_MODELS` is only a degraded-mode
|
|
8
8
|
* fallback for when there is no API key or discovery fails.
|
|
9
9
|
*/
|
|
10
|
-
import { getCachedCatalog, type ModelCatalogEntry } from "./cloud-direct";
|
|
10
|
+
import { getCachedCatalog, type CacheEntry, type ModelCatalogEntry } from "./cloud-direct";
|
|
11
11
|
|
|
12
12
|
const DEFAULT_HOST = "https://server.codeium.com";
|
|
13
13
|
|
|
@@ -66,6 +66,8 @@ export const DEVIN_MODEL_CONTEXT_WINDOWS: Record<string, number> = {
|
|
|
66
66
|
"claude-opus-5-5": 1_000_000,
|
|
67
67
|
"claude-opus-5": 1_000_000,
|
|
68
68
|
"claude-fable-5-1": 1_000_000,
|
|
69
|
+
// 260929 preemptive: Claude Sonnet 5.5 (1M per Anthropic) seeded before Devin's live catalog lists it.
|
|
70
|
+
"claude-sonnet-5-5": 1_000_000,
|
|
69
71
|
"claude-sonnet-5": 1_000_000,
|
|
70
72
|
"glm-5-2": 200_000,
|
|
71
73
|
"glm-5-3": 1_048_576,
|
|
@@ -103,7 +105,7 @@ export function collapseDevinModelUid(uid: string): string {
|
|
|
103
105
|
* Offering them on a reasoning control would name a setting that does something
|
|
104
106
|
* else, so the collapse keeps stripping them while the ladder ignores them.
|
|
105
107
|
*/
|
|
106
|
-
const REASONING_RUNG_TOKENS = new Set(["none", "low", "medium", "high", "xhigh", "max"]);
|
|
108
|
+
const REASONING_RUNG_TOKENS = new Set(["none", "minimal", "low", "medium", "high", "xhigh", "max"]);
|
|
107
109
|
|
|
108
110
|
/**
|
|
109
111
|
* The reasoning rungs a catalog UID carries, in ladder order.
|
|
@@ -124,11 +126,154 @@ export function devinReasoningRungsOf(uid: string): string[] {
|
|
|
124
126
|
}
|
|
125
127
|
|
|
126
128
|
/** Ladder order for display, matching the Codex rung order. */
|
|
127
|
-
const RUNG_ORDER = ["none", "low", "medium", "high", "xhigh", "max"];
|
|
129
|
+
const RUNG_ORDER = ["none", "minimal", "low", "medium", "high", "xhigh", "max"];
|
|
128
130
|
export function sortDevinRungs(rungs: Iterable<string>): string[] {
|
|
129
131
|
return [...new Set(rungs)].sort((a, b) => RUNG_ORDER.indexOf(a) - RUNG_ORDER.indexOf(b));
|
|
130
132
|
}
|
|
131
133
|
|
|
134
|
+
/**
|
|
135
|
+
* Effort rungs a catalog family axis can name, in ladder order.
|
|
136
|
+
* The catalog spells `Minimal` (Gemini Flash) and `No Thinking` (GLM), which
|
|
137
|
+
* resolution and the picker place alongside the usual Codex rungs.
|
|
138
|
+
*/
|
|
139
|
+
const FAMILY_EFFORT_LADDER = ["none", "minimal", "low", "medium", "high", "xhigh", "max"];
|
|
140
|
+
const EFFORT_AXES = ["Effort", "Reasoning Effort"];
|
|
141
|
+
const FAST_AXIS = "Fast Mode";
|
|
142
|
+
const LONG_CONTEXT_AXIS = "1M Context";
|
|
143
|
+
const THINKING_AXIS = "Thinking";
|
|
144
|
+
|
|
145
|
+
/** The picker id of a catalog family: its uid with dotted versions hyphenated, as the row uids spell it. */
|
|
146
|
+
export function devinFamilyBaseId(familyUid: string): string {
|
|
147
|
+
return familyUid.replace(/\./g, "-");
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
function isEffortAxis(axis: string): boolean {
|
|
151
|
+
return EFFORT_AXES.includes(axis);
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/** The effort rung a family member sits on, read from its axis value name rather than its uid. */
|
|
155
|
+
export function devinFamilyEffortOf(entry: ModelCatalogEntry): string | undefined {
|
|
156
|
+
const axes = entry.familyAxes;
|
|
157
|
+
if (!axes) return undefined;
|
|
158
|
+
for (const axis of EFFORT_AXES) {
|
|
159
|
+
const name = axes[axis]?.name?.toLowerCase();
|
|
160
|
+
if (!name) continue;
|
|
161
|
+
const rung = name === "no thinking" ? "none" : name;
|
|
162
|
+
if (FAMILY_EFFORT_LADDER.includes(rung)) return rung;
|
|
163
|
+
}
|
|
164
|
+
return undefined;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
const familyCache = new WeakMap<CacheEntry, Map<string, ModelCatalogEntry[]>>();
|
|
168
|
+
|
|
169
|
+
/**
|
|
170
|
+
* Group catalog rows by family, keyed by the family's picker id. Only rows
|
|
171
|
+
* that carry family metadata take part; legacy `MODEL_*` rows are internal
|
|
172
|
+
* enum names and never form a picker family.
|
|
173
|
+
*/
|
|
174
|
+
export function devinFamiliesOf(catalog: CacheEntry): Map<string, ModelCatalogEntry[]> {
|
|
175
|
+
let families = familyCache.get(catalog);
|
|
176
|
+
if (families) return families;
|
|
177
|
+
families = new Map();
|
|
178
|
+
for (const entry of catalog.byUid.values()) {
|
|
179
|
+
if (!entry.familyUid || !entry.familyAxes || entry.modelUid.startsWith("MODEL_")) continue;
|
|
180
|
+
const base = devinFamilyBaseId(entry.familyUid);
|
|
181
|
+
const members = families.get(base);
|
|
182
|
+
if (members) members.push(entry);
|
|
183
|
+
else families.set(base, [entry]);
|
|
184
|
+
}
|
|
185
|
+
familyCache.set(catalog, families);
|
|
186
|
+
return families;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
/** What a caller asked for, independent of how the catalog spells it. */
|
|
190
|
+
export interface DevinVariantRequest {
|
|
191
|
+
effort?: string;
|
|
192
|
+
fast?: boolean;
|
|
193
|
+
longContext?: boolean;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
function lexicallyLess(a: number[], b: number[]): boolean {
|
|
197
|
+
for (let i = 0; i < a.length; i++) if (a[i] !== b[i]) return a[i]! < b[i]!;
|
|
198
|
+
return false;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
function effortIndex(rung: string | undefined): number {
|
|
202
|
+
return rung === undefined ? -1 : FAMILY_EFFORT_LADDER.indexOf(rung);
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
/**
|
|
206
|
+
* The member closest to `targets` on the non-effort axes, then the lowest rung
|
|
207
|
+
* at or above `effort`, falling back to the highest rung below it. A missing
|
|
208
|
+
* rung never quietly turns reasoning down: `high` on SWE-1.7 (Medium, Max)
|
|
209
|
+
* selects Max, `xhigh` on SWE-2 selects Max, and `medium` on Kimi K3 (Low,
|
|
210
|
+
* High, Max) selects High.
|
|
211
|
+
*
|
|
212
|
+
* Not lowering a requested effort outranks the non-effort axes: in a family
|
|
213
|
+
* whose Fast rows stop at Medium, `high` plus Fast selects the regular High row
|
|
214
|
+
* rather than dropping to Medium to keep Fast. A member with no effort rung
|
|
215
|
+
* cannot prove it is not lower, so it ranks behind any rung at or above the
|
|
216
|
+
* request. `none` (the bottom of the ladder) is never "lowered", which leaves
|
|
217
|
+
* the Thinking-off target in charge of that request.
|
|
218
|
+
*/
|
|
219
|
+
function closestMember(
|
|
220
|
+
members: ModelCatalogEntry[],
|
|
221
|
+
targets: Record<string, number>,
|
|
222
|
+
effort: string | undefined,
|
|
223
|
+
): ModelCatalogEntry | undefined {
|
|
224
|
+
const want = effortIndex(effort);
|
|
225
|
+
let best: ModelCatalogEntry | undefined;
|
|
226
|
+
let bestScore: number[] | undefined;
|
|
227
|
+
for (const member of members) {
|
|
228
|
+
const axes = member.familyAxes ?? {};
|
|
229
|
+
const mismatches = Object.entries(targets).filter(([axis, order]) => (axes[axis]?.order ?? 0) !== order).length;
|
|
230
|
+
const have = effortIndex(devinFamilyEffortOf(member));
|
|
231
|
+
const lowered = want > 0 && have < want ? 1 : 0;
|
|
232
|
+
const distance = want < 0 ? 0
|
|
233
|
+
: have < 0 ? 2 * FAMILY_EFFORT_LADDER.length
|
|
234
|
+
: have >= want ? have - want : FAMILY_EFFORT_LADDER.length + (want - have);
|
|
235
|
+
const score = [lowered, mismatches, distance, -have];
|
|
236
|
+
if (!bestScore || lexicallyLess(score, bestScore)) {
|
|
237
|
+
best = member;
|
|
238
|
+
bestScore = score;
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
return best;
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
/**
|
|
245
|
+
* Pick the family member for a request. Axes the caller did not ask about
|
|
246
|
+
* (`Fast Mode`, `1M Context`, `Thinking`, `Prompt Cache Retention`, ...) stay
|
|
247
|
+
* where the anchor has them; the anchor is the member the caller named, else
|
|
248
|
+
* the family's catalog default, else the neutral member (every toggle off,
|
|
249
|
+
* effort nearest Medium) for the few families that mark no default. Disabled
|
|
250
|
+
* members are passed over while any enabled one remains, so the pre-flight
|
|
251
|
+
* only reports a tier refusal when the whole family is refused. `includeDisabled`
|
|
252
|
+
* ranks every member, which tells a caller whether a disabled row is itself the
|
|
253
|
+
* best match for the request.
|
|
254
|
+
*/
|
|
255
|
+
export function selectDevinFamilyMember(
|
|
256
|
+
members: ModelCatalogEntry[],
|
|
257
|
+
request: DevinVariantRequest,
|
|
258
|
+
anchor?: ModelCatalogEntry,
|
|
259
|
+
options: { includeDisabled?: boolean } = {},
|
|
260
|
+
): ModelCatalogEntry | undefined {
|
|
261
|
+
const axisNames = new Set(members.flatMap((m) => Object.keys(m.familyAxes ?? {})).filter((a) => !isEffortAxis(a)));
|
|
262
|
+
const base = anchor
|
|
263
|
+
?? members.find((m) => m.isFamilyDefault)
|
|
264
|
+
?? closestMember(members, Object.fromEntries([...axisNames].map((a) => [a, 0])), "medium");
|
|
265
|
+
if (!base) return undefined;
|
|
266
|
+
const targets: Record<string, number> = {};
|
|
267
|
+
for (const axis of axisNames) targets[axis] = base.familyAxes?.[axis]?.order ?? 0;
|
|
268
|
+
if (request.fast && axisNames.has(FAST_AXIS)) targets[FAST_AXIS] = 1;
|
|
269
|
+
if (request.longContext && axisNames.has(LONG_CONTEXT_AXIS)) targets[LONG_CONTEXT_AXIS] = 1;
|
|
270
|
+
// `none` means no reasoning. Families like Claude Sonnet 4.6 express that as
|
|
271
|
+
// Thinking off rather than as an effort rung.
|
|
272
|
+
if (request.effort === "none" && axisNames.has(THINKING_AXIS)) targets[THINKING_AXIS] = 0;
|
|
273
|
+
const enabled = options.includeDisabled ? members : members.filter((m) => !m.disabled);
|
|
274
|
+
return closestMember(enabled.length > 0 ? enabled : members, targets, request.effort ?? devinFamilyEffortOf(base));
|
|
275
|
+
}
|
|
276
|
+
|
|
132
277
|
/**
|
|
133
278
|
* Degraded-mode ladders, used only before the account catalog is readable.
|
|
134
279
|
*
|
|
@@ -158,14 +303,18 @@ export type DevinUsableModelsResult =
|
|
|
158
303
|
models: string[];
|
|
159
304
|
contextWindows: Record<string, number>;
|
|
160
305
|
efforts: Record<string, string[]>;
|
|
306
|
+
/** Per base, the effort of the family's catalog default member, when it is on the ladder. */
|
|
307
|
+
defaultEfforts: Record<string, string>;
|
|
161
308
|
inputModalities: Record<string, string[]>;
|
|
162
309
|
}
|
|
163
310
|
| { ok: false; error: "auth" | "http" | "empty" | "unknown"; detail?: string };
|
|
164
311
|
|
|
165
312
|
/**
|
|
166
313
|
* Fetch the live model roster from Cognition's `GetCascadeModelConfigs` and
|
|
167
|
-
* collapse
|
|
168
|
-
*
|
|
314
|
+
* collapse variants to base ids. Rows that carry family metadata collapse to
|
|
315
|
+
* their family and read their effort from the family's effort axis; only rows
|
|
316
|
+
* without it (older catalogs) fall back to reading suffixes off the uid. The
|
|
317
|
+
* returned list is the authoritative model roster for the signed-in account.
|
|
169
318
|
*/
|
|
170
319
|
export async function fetchDevinUsableModels(opts: {
|
|
171
320
|
apiKey: string;
|
|
@@ -187,9 +336,13 @@ export async function fetchDevinUsableModels(opts: {
|
|
|
187
336
|
// Skip internal enum constants (e.g. MODEL_GPT_5_2_LOW, MODEL_PRIVATE_*).
|
|
188
337
|
// Real chat model UIDs are lowercase dashed strings (swe-1-7, gpt-5-6-sol).
|
|
189
338
|
if (entry.modelUid.startsWith("MODEL_")) continue;
|
|
190
|
-
const
|
|
339
|
+
const family = entry.familyUid && entry.familyAxes ? devinFamilyBaseId(entry.familyUid) : undefined;
|
|
340
|
+
const base = family ?? collapseDevinModelUid(entry.modelUid);
|
|
191
341
|
bases.add(base);
|
|
192
|
-
const
|
|
342
|
+
const familyRung = family ? devinFamilyEffortOf(entry) : undefined;
|
|
343
|
+
const found = family
|
|
344
|
+
? (familyRung && REASONING_RUNG_TOKENS.has(familyRung) ? [familyRung] : [])
|
|
345
|
+
: devinReasoningRungsOf(entry.modelUid);
|
|
193
346
|
if (found.length > 0) {
|
|
194
347
|
let set = rungs.get(base);
|
|
195
348
|
if (!set) { set = new Set(); rungs.set(base, set); }
|
|
@@ -222,13 +375,21 @@ export async function fetchDevinUsableModels(opts: {
|
|
|
222
375
|
// would draw a picker whose only option is the value already in effect.
|
|
223
376
|
if (set.size > 1) efforts[base] = sortDevinRungs(set);
|
|
224
377
|
}
|
|
378
|
+
const defaultEfforts: Record<string, string> = {};
|
|
379
|
+
for (const [base, members] of devinFamiliesOf(catalog)) {
|
|
380
|
+
if (!members.some((member) => member.isFamilyDefault)) continue;
|
|
381
|
+
const selected = selectDevinFamilyMember(members, {});
|
|
382
|
+
const rung = selected && devinFamilyEffortOf(selected);
|
|
383
|
+
if (rung && efforts[base]?.includes(rung)) defaultEfforts[base] = rung;
|
|
384
|
+
}
|
|
225
385
|
// supportsImages arrives tri-state per catalog row, so the collapse votes:
|
|
226
386
|
// a row that never asserted field #5 abstains, which keeps an unsuffixed
|
|
227
387
|
// unknown row from poisoning a base whose effort variants were measured
|
|
228
388
|
// image-capable. Unanimous measured rows advertise; measured disagreement
|
|
229
389
|
// advertises nothing, because a single measured false is not outvoted by
|
|
230
|
-
// its siblings. One accepted mismatch
|
|
231
|
-
// plain UID when the catalog
|
|
390
|
+
// its siblings. One accepted mismatch, only for rows without family
|
|
391
|
+
// metadata: resolveWireModelUid prefers the plain UID when the catalog
|
|
392
|
+
// lists it, so a base advertised
|
|
232
393
|
// ["text","image"] on variant evidence can still route a no-effort request
|
|
233
394
|
// to a plain row that never asserted the field.
|
|
234
395
|
const inputModalities: Record<string, string[]> = {};
|
|
@@ -236,7 +397,7 @@ export async function fetchDevinUsableModels(opts: {
|
|
|
236
397
|
if (votes.sawTrue && votes.sawFalse) continue;
|
|
237
398
|
inputModalities[base] = votes.sawTrue ? ["text", "image"] : ["text"];
|
|
238
399
|
}
|
|
239
|
-
return { ok: true, models: [...bases].sort(), contextWindows, efforts, inputModalities };
|
|
400
|
+
return { ok: true, models: [...bases].sort(), contextWindows, efforts, defaultEfforts, inputModalities };
|
|
240
401
|
} catch (error) {
|
|
241
402
|
const message = error instanceof Error ? error.message : String(error);
|
|
242
403
|
if (/unauth|401|invalid token|login/i.test(message)) return { ok: false, error: "auth", detail: message };
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Devin reasoning signatures across turns.
|
|
3
|
+
*
|
|
4
|
+
* GetChatMessage returns the turn's reasoning attestation as `delta_signature`
|
|
5
|
+
* (#10) together with `delta_signature_type` (#21) in the same frame, and the
|
|
6
|
+
* native client replays both on the assistant prompt as #12 and #18. Measured
|
|
7
|
+
* live on swe-2-high the pair arrives AFTER the visible answer (reasoning, text,
|
|
8
|
+
* then signature), so the Responses layer stores it as its own signature-only
|
|
9
|
+
* reasoning item behind the thinking-text item. GPT and Gemini rows stream no
|
|
10
|
+
* thinking text at all, only the signature.
|
|
11
|
+
*
|
|
12
|
+
* The type is carried inside the stored signature because the reasoning
|
|
13
|
+
* envelope that round-trips through the client keeps a single signature string.
|
|
14
|
+
* A signature stored before this prefix existed replays without a type.
|
|
15
|
+
*/
|
|
16
|
+
import type { OcxAssistantMessage } from "../../types";
|
|
17
|
+
import { isProviderIssuedThinkingSignature } from "../../responses/reasoning-envelope";
|
|
18
|
+
|
|
19
|
+
const TYPED_SIGNATURE_PREFIX = "devin-sig1:";
|
|
20
|
+
|
|
21
|
+
export function encodeDevinSignature(signature: string, signatureType: string | undefined): string {
|
|
22
|
+
return signatureType && !signatureType.includes(":")
|
|
23
|
+
? `${TYPED_SIGNATURE_PREFIX}${signatureType}:${signature}`
|
|
24
|
+
: signature;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export function decodeDevinSignature(stored: string): { signature: string; signatureType?: string } {
|
|
28
|
+
if (!stored.startsWith(TYPED_SIGNATURE_PREFIX)) return { signature: stored };
|
|
29
|
+
const rest = stored.slice(TYPED_SIGNATURE_PREFIX.length);
|
|
30
|
+
const colon = rest.indexOf(":");
|
|
31
|
+
if (colon <= 0) return { signature: stored };
|
|
32
|
+
return { signature: rest.slice(colon + 1), signatureType: rest.slice(0, colon) };
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* The assistant turn's reasoning for ChatMessagePrompt #11/#12/#18.
|
|
37
|
+
*
|
|
38
|
+
* All of the turn's thinking text is replayed. A signature rides along only
|
|
39
|
+
* when it covers exactly that text:
|
|
40
|
+
* - one thinking block carrying its own issued signature (a stray
|
|
41
|
+
* signature-only block does not displace it);
|
|
42
|
+
* - at most one unsigned thinking block plus exactly one signature-only block,
|
|
43
|
+
* which is how a single Devin turn arrives once its late #10 frame has been
|
|
44
|
+
* split into its own reasoning item. With no thinking block at all this is a
|
|
45
|
+
* GPT or Gemini row, where the signature is the only reasoning there is.
|
|
46
|
+
* Any other mix (two signed blocks, a signed block beside unsigned text) has no
|
|
47
|
+
* single attestation for the joined text, so the turn is replayed unsigned.
|
|
48
|
+
*
|
|
49
|
+
* `withholdAnthropic` drops an Anthropic signature and keeps the text: the
|
|
50
|
+
* fallback for a Claude turn Cognition refused (see hasAnthropicSignature).
|
|
51
|
+
*/
|
|
52
|
+
export function devinAssistantReasoning(
|
|
53
|
+
message: OcxAssistantMessage,
|
|
54
|
+
modelId = "",
|
|
55
|
+
withholdAnthropic = false,
|
|
56
|
+
): { thinking?: string; signature?: string; signature_type?: string } {
|
|
57
|
+
const blocks = message.content.filter(
|
|
58
|
+
(part): part is Extract<typeof part, { type: "thinking" }> => part.type === "thinking",
|
|
59
|
+
);
|
|
60
|
+
const textBlocks = blocks.filter(part => Boolean(part.thinking));
|
|
61
|
+
const signatureOnly = blocks.filter(part => !part.thinking && isProviderIssuedThinkingSignature(part.signature));
|
|
62
|
+
const text = textBlocks.map(part => part.thinking).join("\n");
|
|
63
|
+
let stored: string | undefined;
|
|
64
|
+
if (textBlocks.length === 1 && isProviderIssuedThinkingSignature(textBlocks[0]!.signature)) {
|
|
65
|
+
stored = textBlocks[0]!.signature;
|
|
66
|
+
} else if (textBlocks.length === 0 && signatureOnly.length === 1) {
|
|
67
|
+
stored = signatureOnly[0]!.signature;
|
|
68
|
+
} else if (textBlocks.length === 1 && signatureOnly.length === 1) {
|
|
69
|
+
// Only the late trailer shape attests this text. The Responses parser can
|
|
70
|
+
// fold reasoning around a call into one assistant message, so counting
|
|
71
|
+
// blocks without checking their position can attach an unrelated signature.
|
|
72
|
+
const textIndex = message.content.indexOf(textBlocks[0]!);
|
|
73
|
+
if (message.content[textIndex + 1] === signatureOnly[0]) stored = signatureOnly[0]!.signature;
|
|
74
|
+
}
|
|
75
|
+
let decoded = stored ? decodeDevinSignature(stored) : undefined;
|
|
76
|
+
if (decoded && withholdAnthropic && signatureTypeFor(decoded, modelId) === "anthropic") decoded = undefined;
|
|
77
|
+
return {
|
|
78
|
+
...(text ? { thinking: text } : {}),
|
|
79
|
+
...(decoded ? { signature: decoded.signature } : {}),
|
|
80
|
+
...(decoded?.signatureType ? { signature_type: decoded.signatureType } : {}),
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/** A stored signature from before its type was recorded falls back to the model being called. */
|
|
85
|
+
function signatureTypeFor(decoded: { signatureType?: string }, modelId: string): string | undefined {
|
|
86
|
+
return decoded.signatureType ?? (/claude/i.test(modelId) ? "anthropic" : undefined);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* True when the mapped history replays a Claude signature. Cognition streams
|
|
91
|
+
* Claude's thinking as a summary while the signature covers the original, so
|
|
92
|
+
* the pair can fail validation: live on claude-opus-5-5 a signed replay of a
|
|
93
|
+
* visible-thinking turn was refused with `invalid_argument` in 5 of 6 tries and
|
|
94
|
+
* a text-only one in none, while a signed replay that is accepted is what lets
|
|
95
|
+
* the model recall its earlier reasoning. The adapter therefore sends the
|
|
96
|
+
* signature and retries a refusal once without it.
|
|
97
|
+
*/
|
|
98
|
+
export function hasAnthropicSignature(items: ReadonlyArray<{ signature?: string; signature_type?: string }>, modelId: string): boolean {
|
|
99
|
+
return items.some(item => Boolean(item.signature) && signatureTypeFor({ signatureType: item.signature_type }, modelId) === "anthropic");
|
|
100
|
+
}
|