@circuitwall/jarela 2.2.0 → 2.2.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/.next/standalone/.next/BUILD_ID +1 -1
- package/.next/standalone/.next/app-path-routes-manifest.json +2 -2
- package/.next/standalone/.next/build-manifest.json +2 -2
- package/.next/standalone/.next/prerender-manifest.json +3 -3
- package/.next/standalone/.next/server/app/_global-error/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/_global-error.html +1 -1
- package/.next/standalone/.next/server/app/_global-error.rsc +1 -1
- package/.next/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/_not-found.html +1 -1
- package/.next/standalone/.next/server/app/_not-found.rsc +1 -1
- package/.next/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/_not-found.segments/_index.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/api/v1/models/[name]/route.js +61 -20
- package/.next/standalone/.next/server/app/api/v1/models/route.js +5 -3
- package/.next/standalone/.next/server/app/api/v1/providers/[provider]/models/route.js +27 -24
- package/.next/standalone/.next/server/app/api/v1/update/apply/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/page.js +313 -251
- package/.next/standalone/.next/server/app/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/setup/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app-paths-manifest.json +2 -2
- package/.next/standalone/.next/server/chunks/2471.js +17 -17
- package/.next/standalone/.next/server/chunks/2563.js +24 -50
- package/.next/standalone/.next/server/chunks/3399.js +10695 -10695
- package/.next/standalone/.next/server/chunks/3524.js +5 -33
- package/.next/standalone/.next/server/chunks/424.js +10 -5
- package/.next/standalone/.next/server/chunks/4508.js +2 -2
- package/.next/standalone/.next/server/chunks/46.js +111 -46
- package/.next/standalone/.next/server/chunks/466.js +88 -13
- package/.next/standalone/.next/server/chunks/6649.js +1 -1
- package/.next/standalone/.next/server/chunks/9590.js +108 -46
- package/.next/standalone/.next/server/chunks/9640.js +3 -6
- package/.next/standalone/.next/server/middleware-build-manifest.js +2 -2
- package/.next/standalone/.next/server/middleware.js +104 -45
- package/.next/standalone/.next/server/pages/404.html +1 -1
- package/.next/standalone/.next/server/pages/500.html +1 -1
- package/.next/standalone/.next/server/preview-props.json +3 -3
- package/.next/standalone/.next/server/server-reference-manifest.json +1 -1
- package/.next/standalone/.next/static/chunks/{1395-f201e3e2e1f57715.js → 1395-a340fb0a345382a4.js} +89 -14
- package/.next/standalone/.next/static/chunks/{4576-13f7546a145a3624.js → 7045-8bc2f14e430ca661.js} +58 -2
- package/.next/standalone/.next/static/chunks/app/{page-c6e972a7fd13dae8.js → page-7017f07f8a7a083c.js} +170 -151
- package/.next/standalone/package.json +1 -1
- package/CHANGELOG.md +8 -0
- package/api/client.models.test.ts +31 -0
- package/api/client.ts +16 -1
- package/api/types.ts +8 -0
- package/app/api/v1/models/[name]/route.test.ts +89 -0
- package/app/api/v1/models/[name]/route.ts +54 -3
- package/app/api/v1/models/route.ts +4 -2
- package/app/api/v1/providers/[provider]/models/route.test.ts +53 -0
- package/app/api/v1/providers/[provider]/models/route.ts +27 -16
- package/components/agents/agent-editor/AdvancedSection.tsx +8 -2
- package/components/agents/agent-editor/ModelSection.tsx +6 -3
- package/components/models/ModelEditor.tsx +1 -1
- package/components/models/ModelsPanel.tsx +34 -8
- package/components/models/model-editor/GitHubCopilotAuth.tsx +1 -1
- package/components/models/model-editor/save-helpers.ts +2 -2
- package/components/models/model-editor/useModelEditorForm.ts +1 -1
- package/components/models/model-editor/useModelSaveHandlers.ts +1 -1
- package/components/setup/wizard/StepCredentials.tsx +1 -1
- package/components/setup/wizard/StepModel.tsx +3 -8
- package/hooks/useModels.ts +17 -1
- package/lib/agents/effective-model.test.ts +45 -0
- package/lib/agents/effective-model.ts +11 -5
- package/lib/agents/llm.ts +2 -2
- package/lib/agents/run-thread.ts +10 -5
- package/lib/agents/warm-summary-background.test.ts +1 -2
- package/lib/agents/warm-summary-background.ts +4 -7
- package/lib/db/migrations.ts +10 -0
- package/lib/health/probes.test.ts +8 -11
- package/lib/health/probes.ts +5 -31
- package/lib/models/editor-payload.ts +1 -1
- package/lib/providers/github-copilot-auth.ts +2 -3
- package/lib/providers/github-copilot.test.ts +27 -0
- package/lib/providers/github-copilot.ts +21 -44
- package/lib/stores/integrations.test.ts +7 -0
- package/lib/stores/integrations.ts +4 -4
- package/lib/stores/model-config.test.ts +88 -0
- package/lib/stores/model-config.ts +90 -8
- package/package.json +1 -1
- /package/.next/standalone/.next/static/{e2NV1wk6mWg3SIeELXevg → NUrC9xax1sp1Q70nlFqS6}/_buildManifest.js +0 -0
- /package/.next/standalone/.next/static/{e2NV1wk6mWg3SIeELXevg → NUrC9xax1sp1Q70nlFqS6}/_ssgManifest.js +0 -0
|
@@ -3507,14 +3507,14 @@ const INTEGRATIONS = {
|
|
|
3507
3507
|
"github-copilot": {
|
|
3508
3508
|
label: "GitHub Copilot",
|
|
3509
3509
|
category: "llm",
|
|
3510
|
-
description: "Routes chat-completion requests through your Copilot subscription.
|
|
3510
|
+
description: "Routes chat-completion requests through your Copilot subscription. Use the device-flow " + "sign-in in the Models panel; legacy GitHub PAT authentication is no longer supported.",
|
|
3511
3511
|
fields: [
|
|
3512
3512
|
{
|
|
3513
3513
|
key: "api_key",
|
|
3514
|
-
label: "GitHub PAT",
|
|
3515
|
-
placeholder: "
|
|
3514
|
+
label: "Legacy GitHub PAT (unsupported)",
|
|
3515
|
+
placeholder: "Use Models panel sign-in",
|
|
3516
3516
|
secret: true,
|
|
3517
|
-
required:
|
|
3517
|
+
required: false
|
|
3518
3518
|
}
|
|
3519
3519
|
]
|
|
3520
3520
|
},
|
|
@@ -3784,6 +3784,7 @@ function saveIntegration(name, incoming) {
|
|
|
3784
3784
|
if (f.required && !v.trim()) return {
|
|
3785
3785
|
error: `"${f.key}" cannot be empty`
|
|
3786
3786
|
};
|
|
3787
|
+
if (!v.trim()) continue;
|
|
3787
3788
|
merged[f.key] = v;
|
|
3788
3789
|
if (existing[f.key] !== v) touched.push(f.key);
|
|
3789
3790
|
}
|
|
@@ -4361,6 +4362,7 @@ function runMigrations(db) {
|
|
|
4361
4362
|
ensureCredentialsTable(db);
|
|
4362
4363
|
ensureCredentialsLabelAndDefaultColumns(db);
|
|
4363
4364
|
ensureModelConfigCredentialIdColumn(db);
|
|
4365
|
+
ensureModelConfigEnabledColumn(db);
|
|
4364
4366
|
ensureAgentConfigsToolCredentialsColumn(db);
|
|
4365
4367
|
seedAgentConfigs(db);
|
|
4366
4368
|
backfillDeveloperInteractiveTerminalTools(db);
|
|
@@ -5347,6 +5349,14 @@ function ensureModelConfigCredentialIdColumn(db) {
|
|
|
5347
5349
|
db.exec("ALTER TABLE model_configs ADD COLUMN credential_id TEXT");
|
|
5348
5350
|
}
|
|
5349
5351
|
}
|
|
5352
|
+
// ADR-0102: disabled model configs stay saved but are hidden from model
|
|
5353
|
+
// selection, routing, and default fallback. Existing rows default to enabled.
|
|
5354
|
+
function ensureModelConfigEnabledColumn(db) {
|
|
5355
|
+
const cols = db.prepare("PRAGMA table_info(model_configs)").all();
|
|
5356
|
+
if (!cols.some((c)=>c.name === "enabled")) {
|
|
5357
|
+
db.exec("ALTER TABLE model_configs ADD COLUMN enabled INTEGER NOT NULL DEFAULT 1");
|
|
5358
|
+
}
|
|
5359
|
+
}
|
|
5350
5360
|
// Idempotent backfill: for each model_config still carrying an inline
|
|
5351
5361
|
// `api_key` (and no `credential_id` yet), create a credential row and
|
|
5352
5362
|
// link by id. Strips the api_key / base_url / extra_headers fields from
|
|
@@ -6724,11 +6734,14 @@ __webpack_require__.d(__webpack_exports__, {
|
|
|
6724
6734
|
lB: () => (/* binding */ getDefaultModelConfig),
|
|
6725
6735
|
Ac: () => (/* binding */ getModelConfig),
|
|
6726
6736
|
mN: () => (/* binding */ getModelParams),
|
|
6737
|
+
FO: () => (/* binding */ isModelConfigEnabled),
|
|
6738
|
+
"if": () => (/* binding */ listEnabledModelConfigs),
|
|
6727
6739
|
D1: () => (/* binding */ listModelConfigs),
|
|
6740
|
+
np: () => (/* binding */ resolveRunnableModelConfig),
|
|
6728
6741
|
TN: () => (/* binding */ upsertModelConfig)
|
|
6729
6742
|
});
|
|
6730
6743
|
|
|
6731
|
-
// UNUSED EXPORTS: deleteModelConfig, withModelConfigMutation
|
|
6744
|
+
// UNUSED EXPORTS: ModelConfigStateError, deleteModelConfig, setDefaultModelConfig, setModelConfigEnabled, withModelConfigMutation
|
|
6732
6745
|
|
|
6733
6746
|
// EXTERNAL MODULE: ./lib/db/index.ts + 7 modules
|
|
6734
6747
|
var lib_db = __webpack_require__(39504);
|
|
@@ -6767,6 +6780,14 @@ const now = ()=>new Date().toISOString();
|
|
|
6767
6780
|
function withModelConfigMutation(name, operation) {
|
|
6768
6781
|
return withKeyedMutation(`model-config:${name}`, operation);
|
|
6769
6782
|
}
|
|
6783
|
+
class ModelConfigStateError extends Error {
|
|
6784
|
+
constructor(message, status){
|
|
6785
|
+
super(message), this.status = status;
|
|
6786
|
+
}
|
|
6787
|
+
}
|
|
6788
|
+
function isModelConfigEnabled(cfg) {
|
|
6789
|
+
return !!cfg && cfg.enabled !== 0;
|
|
6790
|
+
}
|
|
6770
6791
|
// `params` holds the provider API key alongside hyperparameters and is
|
|
6771
6792
|
// stored encrypted at rest (ADR-0005). Decrypt before returning rows.
|
|
6772
6793
|
function decryptRow(row) {
|
|
@@ -6779,18 +6800,74 @@ function listModelConfigs() {
|
|
|
6779
6800
|
const rows = (0,lib_db/* getDb */.L)().prepare("SELECT * FROM model_configs ORDER BY is_default DESC, name ASC").all();
|
|
6780
6801
|
return rows.map(decryptRow);
|
|
6781
6802
|
}
|
|
6803
|
+
/** Model configs that may be offered for selection or routing. */ function listEnabledModelConfigs() {
|
|
6804
|
+
return listModelConfigs().filter(isModelConfigEnabled);
|
|
6805
|
+
}
|
|
6782
6806
|
function getModelConfig(name) {
|
|
6783
6807
|
const row = (0,lib_db/* getDb */.L)().prepare("SELECT * FROM model_configs WHERE name=?").get(name) ?? null;
|
|
6784
6808
|
return row ? decryptRow(row) : null;
|
|
6785
6809
|
}
|
|
6786
6810
|
function getDefaultModelConfig() {
|
|
6787
6811
|
const db = (0,lib_db/* getDb */.L)();
|
|
6788
|
-
const row = db.prepare("SELECT * FROM model_configs WHERE is_default=1 LIMIT 1").get() ?? null;
|
|
6812
|
+
const row = db.prepare("SELECT * FROM model_configs WHERE is_default=1 AND enabled=1 LIMIT 1").get() ?? null;
|
|
6789
6813
|
if (row) return decryptRow(row);
|
|
6790
|
-
const fallback = db.prepare("SELECT * FROM model_configs ORDER BY name ASC LIMIT 1").get() ?? null;
|
|
6814
|
+
const fallback = db.prepare("SELECT * FROM model_configs WHERE enabled=1 ORDER BY name ASC LIMIT 1").get() ?? null;
|
|
6791
6815
|
return fallback ? decryptRow(fallback) : null;
|
|
6792
6816
|
}
|
|
6793
|
-
|
|
6817
|
+
/**
|
|
6818
|
+
* Resolve the config a run should use: the named config when it exists and is
|
|
6819
|
+
* enabled, otherwise the workspace default. A disabled assignment therefore
|
|
6820
|
+
* falls back without being cleared, so re-enabling restores it.
|
|
6821
|
+
*/ function resolveRunnableModelConfig(name) {
|
|
6822
|
+
const named = name ? getModelConfig(name) : null;
|
|
6823
|
+
if (named && isModelConfigEnabled(named)) return named;
|
|
6824
|
+
return getDefaultModelConfig();
|
|
6825
|
+
}
|
|
6826
|
+
/** Atomically make `name` the only default model config. */ function setDefaultModelConfig(name) {
|
|
6827
|
+
return withDbTransaction(()=>{
|
|
6828
|
+
const db = getDb();
|
|
6829
|
+
const target = getModelConfig(name);
|
|
6830
|
+
if (!target) throw new ModelConfigStateError(`Model config "${name}" not found`, 404);
|
|
6831
|
+
if (!isModelConfigEnabled(target)) {
|
|
6832
|
+
throw new ModelConfigStateError(`Model config "${name}" is disabled; enable it before making it the default`, 409);
|
|
6833
|
+
}
|
|
6834
|
+
const t = now();
|
|
6835
|
+
db.prepare("UPDATE model_configs SET is_default=0, updated_at=? WHERE is_default=1 AND name<>?").run(t, name);
|
|
6836
|
+
db.prepare("UPDATE model_configs SET is_default=1, updated_at=? WHERE name=?").run(t, name);
|
|
6837
|
+
return getModelConfig(name);
|
|
6838
|
+
});
|
|
6839
|
+
}
|
|
6840
|
+
/**
|
|
6841
|
+
* Enable or disable a model config. Disabling the default promotes the
|
|
6842
|
+
* alphabetically-first other enabled config in the same transaction, and the
|
|
6843
|
+
* last enabled config cannot be disabled so runs always have a model.
|
|
6844
|
+
*/ function setModelConfigEnabled(name, enabled) {
|
|
6845
|
+
return withDbTransaction(()=>{
|
|
6846
|
+
const db = getDb();
|
|
6847
|
+
const target = getModelConfig(name);
|
|
6848
|
+
if (!target) throw new ModelConfigStateError(`Model config "${name}" not found`, 404);
|
|
6849
|
+
if (isModelConfigEnabled(target) === enabled) return target;
|
|
6850
|
+
const t = now();
|
|
6851
|
+
if (enabled) {
|
|
6852
|
+
db.prepare("UPDATE model_configs SET enabled=1, updated_at=? WHERE name=?").run(t, name);
|
|
6853
|
+
return getModelConfig(name);
|
|
6854
|
+
}
|
|
6855
|
+
const next = db.prepare("SELECT name FROM model_configs WHERE enabled=1 AND name<>? ORDER BY name ASC LIMIT 1").get(name);
|
|
6856
|
+
if (!next?.name) {
|
|
6857
|
+
throw new ModelConfigStateError("At least one model config must stay enabled", 409);
|
|
6858
|
+
}
|
|
6859
|
+
db.prepare("UPDATE model_configs SET enabled=0, is_default=0, updated_at=? WHERE name=?").run(t, name);
|
|
6860
|
+
if (target.is_default === 1) {
|
|
6861
|
+
db.prepare("UPDATE model_configs SET is_default=1, updated_at=? WHERE name=?").run(t, next.name);
|
|
6862
|
+
}
|
|
6863
|
+
return getModelConfig(name);
|
|
6864
|
+
});
|
|
6865
|
+
}
|
|
6866
|
+
/**
|
|
6867
|
+
* Insert or update a model config. `is_default` undefined keeps the current
|
|
6868
|
+
* flag (false for new rows); true atomically clears every other default and
|
|
6869
|
+
* re-enables the row, because the default must always be selectable.
|
|
6870
|
+
*/ function upsertModelConfig(name, provider, model_id, params, is_default, credential_id = null) {
|
|
6794
6871
|
return (0,transaction/* withDbTransaction */.J)(()=>{
|
|
6795
6872
|
const t = now();
|
|
6796
6873
|
const existing = getModelConfig(name);
|
|
@@ -6806,9 +6883,11 @@ function upsertModelConfig(name, provider, model_id, params, is_default, credent
|
|
|
6806
6883
|
...decodeInlineParams(existing),
|
|
6807
6884
|
...params
|
|
6808
6885
|
};
|
|
6886
|
+
const finalDefault = is_default ?? existing?.is_default === 1;
|
|
6887
|
+
const finalEnabled = finalDefault || !existing || isModelConfigEnabled(existing);
|
|
6809
6888
|
const db = (0,lib_db/* getDb */.L)();
|
|
6810
|
-
if (
|
|
6811
|
-
db.prepare("INSERT OR REPLACE INTO model_configs (name,provider,model_id,params,is_default,credential_id,created_at,updated_at) VALUES (
|
|
6889
|
+
if (finalDefault) db.prepare("UPDATE model_configs SET is_default=0 WHERE name<>?").run(name);
|
|
6890
|
+
db.prepare("INSERT OR REPLACE INTO model_configs (name,provider,model_id,params,is_default,enabled,credential_id,created_at,updated_at) VALUES (?,?,?,?,?,?,?,?,?)").run(name, provider, model_id, (0,envelope/* encrypt */.w)(JSON.stringify(mergedParams)), finalDefault ? 1 : 0, finalEnabled ? 1 : 0, finalCredId, created_at, t);
|
|
6812
6891
|
return getModelConfig(name);
|
|
6813
6892
|
});
|
|
6814
6893
|
}
|
|
@@ -6821,10 +6900,10 @@ function deleteModelConfig(name) {
|
|
|
6821
6900
|
// Automatically reset any agent pointing to this deleted model config to NULL (automatic / default model)
|
|
6822
6901
|
db.prepare("UPDATE agent_configs SET model_config_name = NULL WHERE model_config_name = ?").run(name);
|
|
6823
6902
|
// Avoid the "no default" state: if the deleted row was the default and any
|
|
6824
|
-
// other rows remain, promote the alphabetically-first
|
|
6903
|
+
// other enabled rows remain, promote the alphabetically-first one so
|
|
6825
6904
|
// agents that fall back to the default keep working.
|
|
6826
6905
|
if (wasDefault) {
|
|
6827
|
-
const next = db.prepare("SELECT name FROM model_configs ORDER BY name ASC LIMIT 1").get();
|
|
6906
|
+
const next = db.prepare("SELECT name FROM model_configs WHERE enabled=1 ORDER BY name ASC LIMIT 1").get();
|
|
6828
6907
|
if (next?.name) db.prepare("UPDATE model_configs SET is_default=1 WHERE name=?").run(next.name);
|
|
6829
6908
|
}
|
|
6830
6909
|
return true;
|
|
@@ -7991,17 +8070,17 @@ function pickGitHubCompatOptions(params) {
|
|
|
7991
8070
|
return out;
|
|
7992
8071
|
}
|
|
7993
8072
|
const tokenCache = new Map();
|
|
7994
|
-
function
|
|
7995
|
-
return token.startsWith("ghp_") || token.startsWith("github_pat_")
|
|
8073
|
+
function isGitHubPat(token) {
|
|
8074
|
+
return token.startsWith("ghp_") || token.startsWith("github_pat_");
|
|
7996
8075
|
}
|
|
7997
|
-
async function
|
|
7998
|
-
if (!
|
|
7999
|
-
const cached = tokenCache.get(
|
|
8076
|
+
async function exchangeCopilotSessionToken(githubToken) {
|
|
8077
|
+
if (!githubToken) throw new Error("GitHub Copilot: no API key configured for token exchange.");
|
|
8078
|
+
const cached = tokenCache.get(githubToken);
|
|
8000
8079
|
if (cached && cached.expiresAt - Date.now() > 60000) return cached.token;
|
|
8001
|
-
tokenCache.delete(
|
|
8080
|
+
tokenCache.delete(githubToken);
|
|
8002
8081
|
const res = await fetch("https://api.github.com/copilot_internal/v2/token", {
|
|
8003
8082
|
headers: {
|
|
8004
|
-
Authorization: `token ${
|
|
8083
|
+
Authorization: `token ${githubToken}`,
|
|
8005
8084
|
"User-Agent": "Jarela/1.0"
|
|
8006
8085
|
},
|
|
8007
8086
|
signal: AbortSignal.timeout(30000)
|
|
@@ -8009,17 +8088,17 @@ async function getCopilotToken(pat) {
|
|
|
8009
8088
|
if (!res.ok) {
|
|
8010
8089
|
const body = await (0,error/* readErrorBody */.N)(res);
|
|
8011
8090
|
if (res.status === 401) {
|
|
8012
|
-
throw new Error(`GitHub Copilot: token exchange failed (401). ` + `
|
|
8091
|
+
throw new Error(`GitHub Copilot: token exchange failed (401). ` + `Check that this GitHub account has an active Copilot subscription and the credential is valid. ` + `Details: ${body}`);
|
|
8013
8092
|
}
|
|
8014
8093
|
if (res.status === 404) {
|
|
8015
|
-
throw new Error("GitHub Copilot
|
|
8094
|
+
throw new Error("GitHub Copilot could not exchange this credential for a session token. " + "Use the in-app device-flow sign-in or set params.copilot_session_token.");
|
|
8016
8095
|
}
|
|
8017
8096
|
throw new Error(`GitHub Copilot: token exchange failed (${res.status}): ${body}`);
|
|
8018
8097
|
}
|
|
8019
8098
|
const json = await res.json();
|
|
8020
8099
|
if (!json.token) throw new Error("GitHub Copilot: token exchange returned no token");
|
|
8021
8100
|
const expiresAt = json.expires_at ? new Date(json.expires_at).getTime() : Date.now() + 28 * 60000;
|
|
8022
|
-
tokenCache.set(
|
|
8101
|
+
tokenCache.set(githubToken, {
|
|
8023
8102
|
token: json.token,
|
|
8024
8103
|
expiresAt
|
|
8025
8104
|
});
|
|
@@ -8050,7 +8129,6 @@ async function resolveCopilotAuth(params) {
|
|
|
8050
8129
|
const explicitSessionToken = params.copilot_session_token?.trim();
|
|
8051
8130
|
if (explicitSessionToken && explicitSessionToken.length > 0) {
|
|
8052
8131
|
return {
|
|
8053
|
-
kind: "copilot-api",
|
|
8054
8132
|
baseURL: params.base_url ?? "https://api.githubcopilot.com",
|
|
8055
8133
|
apiKey: explicitSessionToken,
|
|
8056
8134
|
headers: {
|
|
@@ -8060,9 +8138,8 @@ async function resolveCopilotAuth(params) {
|
|
|
8060
8138
|
}
|
|
8061
8139
|
const oauthToken = (0,github_copilot_auth/* getStoredOAuthToken */.OS)();
|
|
8062
8140
|
if (oauthToken) {
|
|
8063
|
-
const token = await
|
|
8141
|
+
const token = await exchangeCopilotSessionToken(oauthToken);
|
|
8064
8142
|
return {
|
|
8065
|
-
kind: "copilot-api",
|
|
8066
8143
|
baseURL: params.base_url ?? "https://api.githubcopilot.com",
|
|
8067
8144
|
apiKey: token,
|
|
8068
8145
|
headers: {
|
|
@@ -8070,23 +8147,14 @@ async function resolveCopilotAuth(params) {
|
|
|
8070
8147
|
}
|
|
8071
8148
|
};
|
|
8072
8149
|
}
|
|
8073
|
-
if (apiKey &&
|
|
8074
|
-
|
|
8075
|
-
kind: "models-rest",
|
|
8076
|
-
baseURL: params.base_url ?? "https://models.github.ai/inference",
|
|
8077
|
-
apiKey,
|
|
8078
|
-
headers: {
|
|
8079
|
-
Accept: "application/vnd.github+json",
|
|
8080
|
-
"X-GitHub-Api-Version": "2026-03-10"
|
|
8081
|
-
}
|
|
8082
|
-
};
|
|
8150
|
+
if (apiKey && isGitHubPat(apiKey)) {
|
|
8151
|
+
throw new Error("GitHub Copilot: this provider's GitHub PAT route used GitHub Models, which has been retired. " + "Use the in-app device-flow sign-in or set copilot_session_token.");
|
|
8083
8152
|
}
|
|
8084
8153
|
if (!apiKey) {
|
|
8085
8154
|
throw new Error("GitHub Copilot: not signed in. Use the in-app device-flow login, or set api_key / copilot_session_token.");
|
|
8086
8155
|
}
|
|
8087
|
-
const token = await
|
|
8156
|
+
const token = await exchangeCopilotSessionToken(apiKey);
|
|
8088
8157
|
return {
|
|
8089
|
-
kind: "copilot-api",
|
|
8090
8158
|
baseURL: params.base_url ?? "https://api.githubcopilot.com",
|
|
8091
8159
|
apiKey: token,
|
|
8092
8160
|
headers: {
|
|
@@ -8097,14 +8165,9 @@ async function resolveCopilotAuth(params) {
|
|
|
8097
8165
|
// Anthropic SDK client pointed at Copilot's native `/v1/messages` endpoint.
|
|
8098
8166
|
// Copilot accepts the Anthropic request shape (including `cache_control`
|
|
8099
8167
|
// ephemeral breakpoints) for Claude-family models when called with a
|
|
8100
|
-
// session token.
|
|
8101
|
-
// this endpoint, so we surface a clear error there instead of silently
|
|
8102
|
-
// downgrading to a path that strips the cache markers.
|
|
8168
|
+
// Copilot session token.
|
|
8103
8169
|
async function resolvedAnthropicClient(params) {
|
|
8104
8170
|
const auth = await resolveCopilotAuth(params);
|
|
8105
|
-
if (auth.kind !== "copilot-api") {
|
|
8106
|
-
throw new Error("GitHub Copilot: Claude models require a Copilot session token (OAuth or copilot_session_token). " + "Personal Access Tokens route to GitHub Models REST, which does not expose the native Messages API.");
|
|
8107
|
-
}
|
|
8108
8171
|
return new client/* Anthropic */.NB({
|
|
8109
8172
|
// The SDK requires an apiKey field; we authenticate via authToken
|
|
8110
8173
|
// (Authorization: Bearer ...) per Copilot's contract. The empty
|
|
@@ -10599,9 +10662,8 @@ function isRetryableProviderStreamError(msg) {
|
|
|
10599
10662
|
/* harmony export */ });
|
|
10600
10663
|
/* unused harmony exports startDeviceFlow, pollDeviceFlow, storeOAuthToken, clearStoredOAuthToken, getAuthStatus */
|
|
10601
10664
|
/* harmony import */ var _lib_stores_memory__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(423);
|
|
10602
|
-
// GitHub Copilot device-flow login.
|
|
10603
|
-
//
|
|
10604
|
-
// Copilot session token and gets rate-limited on the Models REST API).
|
|
10665
|
+
// GitHub Copilot device-flow login. The legacy PAT path depended on GitHub
|
|
10666
|
+
// Models, which has been retired; use this flow for Copilot Chat access.
|
|
10605
10667
|
//
|
|
10606
10668
|
// Flow (RFC 8628):
|
|
10607
10669
|
// 1. POST github.com/login/device/code with the VS Code Copilot client_id.
|
|
@@ -1819,8 +1819,7 @@ async function findTopicBoundary(threadId, boundarySeq) {
|
|
|
1819
1819
|
if (!thread) return null;
|
|
1820
1820
|
const agent = (0,_lib_stores_agent_configs__WEBPACK_IMPORTED_MODULE_2__/* .getAgentConfig */ .mJ)(thread.agent_id);
|
|
1821
1821
|
if (!agent) return null;
|
|
1822
|
-
const
|
|
1823
|
-
const modelCfg = modelName ? (0,_lib_stores_model_config__WEBPACK_IMPORTED_MODULE_3__/* .getModelConfig */ .Ac)(modelName) : null;
|
|
1822
|
+
const modelCfg = (0,_lib_stores_model_config__WEBPACK_IMPORTED_MODULE_3__/* .resolveRunnableModelConfig */ .np)(agent.model_config_name);
|
|
1824
1823
|
if (!modelCfg?.provider || !modelCfg.model_id) return null;
|
|
1825
1824
|
const rows = (0,_lib_stores_threads__WEBPACK_IMPORTED_MODULE_5__/* .getRecentMessagesWindow */ .S0)(threadId, 0, undefined, "foreground").filter((row)=>row.role === "user" || row.role === "assistant");
|
|
1826
1825
|
const boundaryIndex = rows.findIndex((row)=>row.seq >= boundarySeq);
|
|
@@ -1936,8 +1935,7 @@ async function compactThreadWarmContext(threadId, requestedBoundarySeq, options
|
|
|
1936
1935
|
if (!thread) return null;
|
|
1937
1936
|
const agent = (0,_lib_stores_agent_configs__WEBPACK_IMPORTED_MODULE_2__/* .getAgentConfig */ .mJ)(thread.agent_id);
|
|
1938
1937
|
if (!agent) return null;
|
|
1939
|
-
const
|
|
1940
|
-
const modelCfg = modelName ? (0,_lib_stores_model_config__WEBPACK_IMPORTED_MODULE_3__/* .getModelConfig */ .Ac)(modelName) : null;
|
|
1938
|
+
const modelCfg = (0,_lib_stores_model_config__WEBPACK_IMPORTED_MODULE_3__/* .resolveRunnableModelConfig */ .np)(agent.model_config_name);
|
|
1941
1939
|
if (!modelCfg?.provider || !modelCfg.model_id) return null;
|
|
1942
1940
|
const baseParams = providerOverride?.params ?? (0,_lib_stores_model_config__WEBPACK_IMPORTED_MODULE_3__/* .getModelParams */ .mN)(modelCfg);
|
|
1943
1941
|
const tier = (0,_lib_stores_agent_configs__WEBPACK_IMPORTED_MODULE_2__/* .getAgentTierProportions */ .fg)(agent);
|
|
@@ -2056,8 +2054,7 @@ async function buildAutomationChannelSummaryBefore(threadId, channel, boundarySe
|
|
|
2056
2054
|
if (!thread) return null;
|
|
2057
2055
|
const agent = (0,_lib_stores_agent_configs__WEBPACK_IMPORTED_MODULE_2__/* .getAgentConfig */ .mJ)(thread.agent_id);
|
|
2058
2056
|
if (!agent) return null;
|
|
2059
|
-
const
|
|
2060
|
-
const modelCfg = modelName ? (0,_lib_stores_model_config__WEBPACK_IMPORTED_MODULE_3__/* .getModelConfig */ .Ac)(modelName) : null;
|
|
2057
|
+
const modelCfg = (0,_lib_stores_model_config__WEBPACK_IMPORTED_MODULE_3__/* .resolveRunnableModelConfig */ .np)(agent.model_config_name);
|
|
2061
2058
|
if (!modelCfg?.provider || !modelCfg.model_id) return null;
|
|
2062
2059
|
const baseParams = providerOverride?.params ?? (0,_lib_stores_model_config__WEBPACK_IMPORTED_MODULE_3__/* .getModelParams */ .mN)(modelCfg);
|
|
2063
2060
|
const tier = (0,_lib_stores_agent_configs__WEBPACK_IMPORTED_MODULE_2__/* .getAgentTierProportions */ .fg)(agent);
|
|
@@ -4,8 +4,8 @@ globalThis.__BUILD_MANIFEST = {
|
|
|
4
4
|
],
|
|
5
5
|
"devFiles": [],
|
|
6
6
|
"lowPriorityFiles": [
|
|
7
|
-
"static/
|
|
8
|
-
"static/
|
|
7
|
+
"static/NUrC9xax1sp1Q70nlFqS6/_buildManifest.js",
|
|
8
|
+
"static/NUrC9xax1sp1Q70nlFqS6/_ssgManifest.js"
|
|
9
9
|
],
|
|
10
10
|
"rootMainFiles": [
|
|
11
11
|
"static/chunks/webpack-22f2423f357dd0a1.js",
|
|
@@ -49442,6 +49442,7 @@ function runMigrations(db) {
|
|
|
49442
49442
|
ensureCredentialsTable(db);
|
|
49443
49443
|
ensureCredentialsLabelAndDefaultColumns(db);
|
|
49444
49444
|
ensureModelConfigCredentialIdColumn(db);
|
|
49445
|
+
ensureModelConfigEnabledColumn(db);
|
|
49445
49446
|
ensureAgentConfigsToolCredentialsColumn(db);
|
|
49446
49447
|
seedAgentConfigs(db);
|
|
49447
49448
|
backfillDeveloperInteractiveTerminalTools(db);
|
|
@@ -50428,6 +50429,14 @@ function ensureModelConfigCredentialIdColumn(db) {
|
|
|
50428
50429
|
db.exec("ALTER TABLE model_configs ADD COLUMN credential_id TEXT");
|
|
50429
50430
|
}
|
|
50430
50431
|
}
|
|
50432
|
+
// ADR-0102: disabled model configs stay saved but are hidden from model
|
|
50433
|
+
// selection, routing, and default fallback. Existing rows default to enabled.
|
|
50434
|
+
function ensureModelConfigEnabledColumn(db) {
|
|
50435
|
+
const cols = db.prepare("PRAGMA table_info(model_configs)").all();
|
|
50436
|
+
if (!cols.some((c)=>c.name === "enabled")) {
|
|
50437
|
+
db.exec("ALTER TABLE model_configs ADD COLUMN enabled INTEGER NOT NULL DEFAULT 1");
|
|
50438
|
+
}
|
|
50439
|
+
}
|
|
50431
50440
|
// Idempotent backfill: for each model_config still carrying an inline
|
|
50432
50441
|
// `api_key` (and no `credential_id` yet), create a credential row and
|
|
50433
50442
|
// link by id. Strips the api_key / base_url / extra_headers fields from
|
|
@@ -97262,9 +97271,8 @@ const geminiProvider = {
|
|
|
97262
97271
|
};
|
|
97263
97272
|
|
|
97264
97273
|
;// ./lib/providers/github-copilot-auth.ts
|
|
97265
|
-
// GitHub Copilot device-flow login.
|
|
97266
|
-
//
|
|
97267
|
-
// Copilot session token and gets rate-limited on the Models REST API).
|
|
97274
|
+
// GitHub Copilot device-flow login. The legacy PAT path depended on GitHub
|
|
97275
|
+
// Models, which has been retired; use this flow for Copilot Chat access.
|
|
97268
97276
|
//
|
|
97269
97277
|
// Flow (RFC 8628):
|
|
97270
97278
|
// 1. POST github.com/login/device/code with the VS Code Copilot client_id.
|
|
@@ -97437,17 +97445,17 @@ function pickGitHubCompatOptions(params) {
|
|
|
97437
97445
|
return out;
|
|
97438
97446
|
}
|
|
97439
97447
|
const tokenCache = new Map();
|
|
97440
|
-
function
|
|
97441
|
-
return token.startsWith("ghp_") || token.startsWith("github_pat_")
|
|
97448
|
+
function isGitHubPat(token) {
|
|
97449
|
+
return token.startsWith("ghp_") || token.startsWith("github_pat_");
|
|
97442
97450
|
}
|
|
97443
|
-
async function
|
|
97444
|
-
if (!
|
|
97445
|
-
const cached = tokenCache.get(
|
|
97451
|
+
async function exchangeCopilotSessionToken(githubToken) {
|
|
97452
|
+
if (!githubToken) throw new Error("GitHub Copilot: no API key configured for token exchange.");
|
|
97453
|
+
const cached = tokenCache.get(githubToken);
|
|
97446
97454
|
if (cached && cached.expiresAt - Date.now() > 60000) return cached.token;
|
|
97447
|
-
tokenCache.delete(
|
|
97455
|
+
tokenCache.delete(githubToken);
|
|
97448
97456
|
const res = await fetch("https://api.github.com/copilot_internal/v2/token", {
|
|
97449
97457
|
headers: {
|
|
97450
|
-
Authorization: `token ${
|
|
97458
|
+
Authorization: `token ${githubToken}`,
|
|
97451
97459
|
"User-Agent": "Jarela/1.0"
|
|
97452
97460
|
},
|
|
97453
97461
|
signal: AbortSignal.timeout(30000)
|
|
@@ -97455,17 +97463,17 @@ async function getCopilotToken(pat) {
|
|
|
97455
97463
|
if (!res.ok) {
|
|
97456
97464
|
const body = await error_readErrorBody(res);
|
|
97457
97465
|
if (res.status === 401) {
|
|
97458
|
-
throw new Error(`GitHub Copilot: token exchange failed (401). ` + `
|
|
97466
|
+
throw new Error(`GitHub Copilot: token exchange failed (401). ` + `Check that this GitHub account has an active Copilot subscription and the credential is valid. ` + `Details: ${body}`);
|
|
97459
97467
|
}
|
|
97460
97468
|
if (res.status === 404) {
|
|
97461
|
-
throw new Error("GitHub Copilot
|
|
97469
|
+
throw new Error("GitHub Copilot could not exchange this credential for a session token. " + "Use the in-app device-flow sign-in or set params.copilot_session_token.");
|
|
97462
97470
|
}
|
|
97463
97471
|
throw new Error(`GitHub Copilot: token exchange failed (${res.status}): ${body}`);
|
|
97464
97472
|
}
|
|
97465
97473
|
const json = await res.json();
|
|
97466
97474
|
if (!json.token) throw new Error("GitHub Copilot: token exchange returned no token");
|
|
97467
97475
|
const expiresAt = json.expires_at ? new Date(json.expires_at).getTime() : Date.now() + 28 * 60000;
|
|
97468
|
-
tokenCache.set(
|
|
97476
|
+
tokenCache.set(githubToken, {
|
|
97469
97477
|
token: json.token,
|
|
97470
97478
|
expiresAt
|
|
97471
97479
|
});
|
|
@@ -97496,7 +97504,6 @@ async function resolveCopilotAuth(params) {
|
|
|
97496
97504
|
const explicitSessionToken = params.copilot_session_token?.trim();
|
|
97497
97505
|
if (explicitSessionToken && explicitSessionToken.length > 0) {
|
|
97498
97506
|
return {
|
|
97499
|
-
kind: "copilot-api",
|
|
97500
97507
|
baseURL: params.base_url ?? "https://api.githubcopilot.com",
|
|
97501
97508
|
apiKey: explicitSessionToken,
|
|
97502
97509
|
headers: {
|
|
@@ -97506,9 +97513,8 @@ async function resolveCopilotAuth(params) {
|
|
|
97506
97513
|
}
|
|
97507
97514
|
const oauthToken = getStoredOAuthToken();
|
|
97508
97515
|
if (oauthToken) {
|
|
97509
|
-
const token = await
|
|
97516
|
+
const token = await exchangeCopilotSessionToken(oauthToken);
|
|
97510
97517
|
return {
|
|
97511
|
-
kind: "copilot-api",
|
|
97512
97518
|
baseURL: params.base_url ?? "https://api.githubcopilot.com",
|
|
97513
97519
|
apiKey: token,
|
|
97514
97520
|
headers: {
|
|
@@ -97516,23 +97522,14 @@ async function resolveCopilotAuth(params) {
|
|
|
97516
97522
|
}
|
|
97517
97523
|
};
|
|
97518
97524
|
}
|
|
97519
|
-
if (apiKey &&
|
|
97520
|
-
|
|
97521
|
-
kind: "models-rest",
|
|
97522
|
-
baseURL: params.base_url ?? "https://models.github.ai/inference",
|
|
97523
|
-
apiKey,
|
|
97524
|
-
headers: {
|
|
97525
|
-
Accept: "application/vnd.github+json",
|
|
97526
|
-
"X-GitHub-Api-Version": "2026-03-10"
|
|
97527
|
-
}
|
|
97528
|
-
};
|
|
97525
|
+
if (apiKey && isGitHubPat(apiKey)) {
|
|
97526
|
+
throw new Error("GitHub Copilot: this provider's GitHub PAT route used GitHub Models, which has been retired. " + "Use the in-app device-flow sign-in or set copilot_session_token.");
|
|
97529
97527
|
}
|
|
97530
97528
|
if (!apiKey) {
|
|
97531
97529
|
throw new Error("GitHub Copilot: not signed in. Use the in-app device-flow login, or set api_key / copilot_session_token.");
|
|
97532
97530
|
}
|
|
97533
|
-
const token = await
|
|
97531
|
+
const token = await exchangeCopilotSessionToken(apiKey);
|
|
97534
97532
|
return {
|
|
97535
|
-
kind: "copilot-api",
|
|
97536
97533
|
baseURL: params.base_url ?? "https://api.githubcopilot.com",
|
|
97537
97534
|
apiKey: token,
|
|
97538
97535
|
headers: {
|
|
@@ -97543,14 +97540,9 @@ async function resolveCopilotAuth(params) {
|
|
|
97543
97540
|
// Anthropic SDK client pointed at Copilot's native `/v1/messages` endpoint.
|
|
97544
97541
|
// Copilot accepts the Anthropic request shape (including `cache_control`
|
|
97545
97542
|
// ephemeral breakpoints) for Claude-family models when called with a
|
|
97546
|
-
// session token.
|
|
97547
|
-
// this endpoint, so we surface a clear error there instead of silently
|
|
97548
|
-
// downgrading to a path that strips the cache markers.
|
|
97543
|
+
// Copilot session token.
|
|
97549
97544
|
async function resolvedAnthropicClient(params) {
|
|
97550
97545
|
const auth = await resolveCopilotAuth(params);
|
|
97551
|
-
if (auth.kind !== "copilot-api") {
|
|
97552
|
-
throw new Error("GitHub Copilot: Claude models require a Copilot session token (OAuth or copilot_session_token). " + "Personal Access Tokens route to GitHub Models REST, which does not expose the native Messages API.");
|
|
97553
|
-
}
|
|
97554
97546
|
return new Anthropic({
|
|
97555
97547
|
// The SDK requires an apiKey field; we authenticate via authToken
|
|
97556
97548
|
// (Authorization: Bearer ...) per Copilot's contract. The empty
|
|
@@ -102982,6 +102974,14 @@ const model_config_now = ()=>new Date().toISOString();
|
|
|
102982
102974
|
function withModelConfigMutation(name, operation) {
|
|
102983
102975
|
return withKeyedMutation(`model-config:${name}`, operation);
|
|
102984
102976
|
}
|
|
102977
|
+
class ModelConfigStateError extends Error {
|
|
102978
|
+
constructor(message, status){
|
|
102979
|
+
super(message), this.status = status;
|
|
102980
|
+
}
|
|
102981
|
+
}
|
|
102982
|
+
function isModelConfigEnabled(cfg) {
|
|
102983
|
+
return !!cfg && cfg.enabled !== 0;
|
|
102984
|
+
}
|
|
102985
102985
|
// `params` holds the provider API key alongside hyperparameters and is
|
|
102986
102986
|
// stored encrypted at rest (ADR-0005). Decrypt before returning rows.
|
|
102987
102987
|
function model_config_decryptRow(row) {
|
|
@@ -102994,18 +102994,74 @@ function listModelConfigs() {
|
|
|
102994
102994
|
const rows = db_getDb().prepare("SELECT * FROM model_configs ORDER BY is_default DESC, name ASC").all();
|
|
102995
102995
|
return rows.map(model_config_decryptRow);
|
|
102996
102996
|
}
|
|
102997
|
+
/** Model configs that may be offered for selection or routing. */ function listEnabledModelConfigs() {
|
|
102998
|
+
return listModelConfigs().filter(isModelConfigEnabled);
|
|
102999
|
+
}
|
|
102997
103000
|
function getModelConfig(name) {
|
|
102998
103001
|
const row = db_getDb().prepare("SELECT * FROM model_configs WHERE name=?").get(name) ?? null;
|
|
102999
103002
|
return row ? model_config_decryptRow(row) : null;
|
|
103000
103003
|
}
|
|
103001
103004
|
function getDefaultModelConfig() {
|
|
103002
103005
|
const db = db_getDb();
|
|
103003
|
-
const row = db.prepare("SELECT * FROM model_configs WHERE is_default=1 LIMIT 1").get() ?? null;
|
|
103006
|
+
const row = db.prepare("SELECT * FROM model_configs WHERE is_default=1 AND enabled=1 LIMIT 1").get() ?? null;
|
|
103004
103007
|
if (row) return model_config_decryptRow(row);
|
|
103005
|
-
const fallback = db.prepare("SELECT * FROM model_configs ORDER BY name ASC LIMIT 1").get() ?? null;
|
|
103008
|
+
const fallback = db.prepare("SELECT * FROM model_configs WHERE enabled=1 ORDER BY name ASC LIMIT 1").get() ?? null;
|
|
103006
103009
|
return fallback ? model_config_decryptRow(fallback) : null;
|
|
103007
103010
|
}
|
|
103008
|
-
|
|
103011
|
+
/**
|
|
103012
|
+
* Resolve the config a run should use: the named config when it exists and is
|
|
103013
|
+
* enabled, otherwise the workspace default. A disabled assignment therefore
|
|
103014
|
+
* falls back without being cleared, so re-enabling restores it.
|
|
103015
|
+
*/ function resolveRunnableModelConfig(name) {
|
|
103016
|
+
const named = name ? getModelConfig(name) : null;
|
|
103017
|
+
if (named && isModelConfigEnabled(named)) return named;
|
|
103018
|
+
return getDefaultModelConfig();
|
|
103019
|
+
}
|
|
103020
|
+
/** Atomically make `name` the only default model config. */ function setDefaultModelConfig(name) {
|
|
103021
|
+
return withDbTransaction(()=>{
|
|
103022
|
+
const db = getDb();
|
|
103023
|
+
const target = getModelConfig(name);
|
|
103024
|
+
if (!target) throw new ModelConfigStateError(`Model config "${name}" not found`, 404);
|
|
103025
|
+
if (!isModelConfigEnabled(target)) {
|
|
103026
|
+
throw new ModelConfigStateError(`Model config "${name}" is disabled; enable it before making it the default`, 409);
|
|
103027
|
+
}
|
|
103028
|
+
const t = model_config_now();
|
|
103029
|
+
db.prepare("UPDATE model_configs SET is_default=0, updated_at=? WHERE is_default=1 AND name<>?").run(t, name);
|
|
103030
|
+
db.prepare("UPDATE model_configs SET is_default=1, updated_at=? WHERE name=?").run(t, name);
|
|
103031
|
+
return getModelConfig(name);
|
|
103032
|
+
});
|
|
103033
|
+
}
|
|
103034
|
+
/**
|
|
103035
|
+
* Enable or disable a model config. Disabling the default promotes the
|
|
103036
|
+
* alphabetically-first other enabled config in the same transaction, and the
|
|
103037
|
+
* last enabled config cannot be disabled so runs always have a model.
|
|
103038
|
+
*/ function setModelConfigEnabled(name, enabled) {
|
|
103039
|
+
return withDbTransaction(()=>{
|
|
103040
|
+
const db = getDb();
|
|
103041
|
+
const target = getModelConfig(name);
|
|
103042
|
+
if (!target) throw new ModelConfigStateError(`Model config "${name}" not found`, 404);
|
|
103043
|
+
if (isModelConfigEnabled(target) === enabled) return target;
|
|
103044
|
+
const t = model_config_now();
|
|
103045
|
+
if (enabled) {
|
|
103046
|
+
db.prepare("UPDATE model_configs SET enabled=1, updated_at=? WHERE name=?").run(t, name);
|
|
103047
|
+
return getModelConfig(name);
|
|
103048
|
+
}
|
|
103049
|
+
const next = db.prepare("SELECT name FROM model_configs WHERE enabled=1 AND name<>? ORDER BY name ASC LIMIT 1").get(name);
|
|
103050
|
+
if (!next?.name) {
|
|
103051
|
+
throw new ModelConfigStateError("At least one model config must stay enabled", 409);
|
|
103052
|
+
}
|
|
103053
|
+
db.prepare("UPDATE model_configs SET enabled=0, is_default=0, updated_at=? WHERE name=?").run(t, name);
|
|
103054
|
+
if (target.is_default === 1) {
|
|
103055
|
+
db.prepare("UPDATE model_configs SET is_default=1, updated_at=? WHERE name=?").run(t, next.name);
|
|
103056
|
+
}
|
|
103057
|
+
return getModelConfig(name);
|
|
103058
|
+
});
|
|
103059
|
+
}
|
|
103060
|
+
/**
|
|
103061
|
+
* Insert or update a model config. `is_default` undefined keeps the current
|
|
103062
|
+
* flag (false for new rows); true atomically clears every other default and
|
|
103063
|
+
* re-enables the row, because the default must always be selectable.
|
|
103064
|
+
*/ function upsertModelConfig(name, provider, model_id, params, is_default, credential_id = null) {
|
|
103009
103065
|
return withDbTransaction(()=>{
|
|
103010
103066
|
const t = model_config_now();
|
|
103011
103067
|
const existing = getModelConfig(name);
|
|
@@ -103021,9 +103077,11 @@ function upsertModelConfig(name, provider, model_id, params, is_default, credent
|
|
|
103021
103077
|
...decodeInlineParams(existing),
|
|
103022
103078
|
...params
|
|
103023
103079
|
};
|
|
103080
|
+
const finalDefault = is_default ?? existing?.is_default === 1;
|
|
103081
|
+
const finalEnabled = finalDefault || !existing || isModelConfigEnabled(existing);
|
|
103024
103082
|
const db = getDb();
|
|
103025
|
-
if (
|
|
103026
|
-
db.prepare("INSERT OR REPLACE INTO model_configs (name,provider,model_id,params,is_default,credential_id,created_at,updated_at) VALUES (
|
|
103083
|
+
if (finalDefault) db.prepare("UPDATE model_configs SET is_default=0 WHERE name<>?").run(name);
|
|
103084
|
+
db.prepare("INSERT OR REPLACE INTO model_configs (name,provider,model_id,params,is_default,enabled,credential_id,created_at,updated_at) VALUES (?,?,?,?,?,?,?,?,?)").run(name, provider, model_id, encrypt(JSON.stringify(mergedParams)), finalDefault ? 1 : 0, finalEnabled ? 1 : 0, finalCredId, created_at, t);
|
|
103027
103085
|
return getModelConfig(name);
|
|
103028
103086
|
});
|
|
103029
103087
|
}
|
|
@@ -103036,10 +103094,10 @@ function deleteModelConfig(name) {
|
|
|
103036
103094
|
// Automatically reset any agent pointing to this deleted model config to NULL (automatic / default model)
|
|
103037
103095
|
db.prepare("UPDATE agent_configs SET model_config_name = NULL WHERE model_config_name = ?").run(name);
|
|
103038
103096
|
// Avoid the "no default" state: if the deleted row was the default and any
|
|
103039
|
-
// other rows remain, promote the alphabetically-first
|
|
103097
|
+
// other enabled rows remain, promote the alphabetically-first one so
|
|
103040
103098
|
// agents that fall back to the default keep working.
|
|
103041
103099
|
if (wasDefault) {
|
|
103042
|
-
const next = db.prepare("SELECT name FROM model_configs ORDER BY name ASC LIMIT 1").get();
|
|
103100
|
+
const next = db.prepare("SELECT name FROM model_configs WHERE enabled=1 ORDER BY name ASC LIMIT 1").get();
|
|
103043
103101
|
if (next?.name) db.prepare("UPDATE model_configs SET is_default=1 WHERE name=?").run(next.name);
|
|
103044
103102
|
}
|
|
103045
103103
|
return true;
|
|
@@ -105446,14 +105504,14 @@ const integrations_INTEGRATIONS = {
|
|
|
105446
105504
|
"github-copilot": {
|
|
105447
105505
|
label: "GitHub Copilot",
|
|
105448
105506
|
category: "llm",
|
|
105449
|
-
description: "Routes chat-completion requests through your Copilot subscription.
|
|
105507
|
+
description: "Routes chat-completion requests through your Copilot subscription. Use the device-flow " + "sign-in in the Models panel; legacy GitHub PAT authentication is no longer supported.",
|
|
105450
105508
|
fields: [
|
|
105451
105509
|
{
|
|
105452
105510
|
key: "api_key",
|
|
105453
|
-
label: "GitHub PAT",
|
|
105454
|
-
placeholder: "
|
|
105511
|
+
label: "Legacy GitHub PAT (unsupported)",
|
|
105512
|
+
placeholder: "Use Models panel sign-in",
|
|
105455
105513
|
secret: true,
|
|
105456
|
-
required:
|
|
105514
|
+
required: false
|
|
105457
105515
|
}
|
|
105458
105516
|
]
|
|
105459
105517
|
},
|
|
@@ -105723,6 +105781,7 @@ function saveIntegration(name, incoming) {
|
|
|
105723
105781
|
if (f.required && !v.trim()) return {
|
|
105724
105782
|
error: `"${f.key}" cannot be empty`
|
|
105725
105783
|
};
|
|
105784
|
+
if (!v.trim()) continue;
|
|
105726
105785
|
merged[f.key] = v;
|
|
105727
105786
|
if (existing[f.key] !== v) touched.push(f.key);
|
|
105728
105787
|
}
|