@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.
Files changed (86) hide show
  1. package/.next/standalone/.next/BUILD_ID +1 -1
  2. package/.next/standalone/.next/app-path-routes-manifest.json +2 -2
  3. package/.next/standalone/.next/build-manifest.json +2 -2
  4. package/.next/standalone/.next/prerender-manifest.json +3 -3
  5. package/.next/standalone/.next/server/app/_global-error/page_client-reference-manifest.js +1 -1
  6. package/.next/standalone/.next/server/app/_global-error.html +1 -1
  7. package/.next/standalone/.next/server/app/_global-error.rsc +1 -1
  8. package/.next/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  9. package/.next/standalone/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
  10. package/.next/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  11. package/.next/standalone/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
  12. package/.next/standalone/.next/server/app/_not-found.html +1 -1
  13. package/.next/standalone/.next/server/app/_not-found.rsc +1 -1
  14. package/.next/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +1 -1
  15. package/.next/standalone/.next/server/app/_not-found.segments/_index.segment.rsc +1 -1
  16. package/.next/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
  17. package/.next/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
  18. package/.next/standalone/.next/server/app/api/v1/models/[name]/route.js +61 -20
  19. package/.next/standalone/.next/server/app/api/v1/models/route.js +5 -3
  20. package/.next/standalone/.next/server/app/api/v1/providers/[provider]/models/route.js +27 -24
  21. package/.next/standalone/.next/server/app/api/v1/update/apply/route.js.nft.json +1 -1
  22. package/.next/standalone/.next/server/app/page.js +313 -251
  23. package/.next/standalone/.next/server/app/page_client-reference-manifest.js +1 -1
  24. package/.next/standalone/.next/server/app/setup/page_client-reference-manifest.js +1 -1
  25. package/.next/standalone/.next/server/app-paths-manifest.json +2 -2
  26. package/.next/standalone/.next/server/chunks/2471.js +17 -17
  27. package/.next/standalone/.next/server/chunks/2563.js +24 -50
  28. package/.next/standalone/.next/server/chunks/3399.js +10695 -10695
  29. package/.next/standalone/.next/server/chunks/3524.js +5 -33
  30. package/.next/standalone/.next/server/chunks/424.js +10 -5
  31. package/.next/standalone/.next/server/chunks/4508.js +2 -2
  32. package/.next/standalone/.next/server/chunks/46.js +111 -46
  33. package/.next/standalone/.next/server/chunks/466.js +88 -13
  34. package/.next/standalone/.next/server/chunks/6649.js +1 -1
  35. package/.next/standalone/.next/server/chunks/9590.js +108 -46
  36. package/.next/standalone/.next/server/chunks/9640.js +3 -6
  37. package/.next/standalone/.next/server/middleware-build-manifest.js +2 -2
  38. package/.next/standalone/.next/server/middleware.js +104 -45
  39. package/.next/standalone/.next/server/pages/404.html +1 -1
  40. package/.next/standalone/.next/server/pages/500.html +1 -1
  41. package/.next/standalone/.next/server/preview-props.json +3 -3
  42. package/.next/standalone/.next/server/server-reference-manifest.json +1 -1
  43. package/.next/standalone/.next/static/chunks/{1395-f201e3e2e1f57715.js → 1395-a340fb0a345382a4.js} +89 -14
  44. package/.next/standalone/.next/static/chunks/{4576-13f7546a145a3624.js → 7045-8bc2f14e430ca661.js} +58 -2
  45. package/.next/standalone/.next/static/chunks/app/{page-c6e972a7fd13dae8.js → page-7017f07f8a7a083c.js} +170 -151
  46. package/.next/standalone/package.json +1 -1
  47. package/CHANGELOG.md +8 -0
  48. package/api/client.models.test.ts +31 -0
  49. package/api/client.ts +16 -1
  50. package/api/types.ts +8 -0
  51. package/app/api/v1/models/[name]/route.test.ts +89 -0
  52. package/app/api/v1/models/[name]/route.ts +54 -3
  53. package/app/api/v1/models/route.ts +4 -2
  54. package/app/api/v1/providers/[provider]/models/route.test.ts +53 -0
  55. package/app/api/v1/providers/[provider]/models/route.ts +27 -16
  56. package/components/agents/agent-editor/AdvancedSection.tsx +8 -2
  57. package/components/agents/agent-editor/ModelSection.tsx +6 -3
  58. package/components/models/ModelEditor.tsx +1 -1
  59. package/components/models/ModelsPanel.tsx +34 -8
  60. package/components/models/model-editor/GitHubCopilotAuth.tsx +1 -1
  61. package/components/models/model-editor/save-helpers.ts +2 -2
  62. package/components/models/model-editor/useModelEditorForm.ts +1 -1
  63. package/components/models/model-editor/useModelSaveHandlers.ts +1 -1
  64. package/components/setup/wizard/StepCredentials.tsx +1 -1
  65. package/components/setup/wizard/StepModel.tsx +3 -8
  66. package/hooks/useModels.ts +17 -1
  67. package/lib/agents/effective-model.test.ts +45 -0
  68. package/lib/agents/effective-model.ts +11 -5
  69. package/lib/agents/llm.ts +2 -2
  70. package/lib/agents/run-thread.ts +10 -5
  71. package/lib/agents/warm-summary-background.test.ts +1 -2
  72. package/lib/agents/warm-summary-background.ts +4 -7
  73. package/lib/db/migrations.ts +10 -0
  74. package/lib/health/probes.test.ts +8 -11
  75. package/lib/health/probes.ts +5 -31
  76. package/lib/models/editor-payload.ts +1 -1
  77. package/lib/providers/github-copilot-auth.ts +2 -3
  78. package/lib/providers/github-copilot.test.ts +27 -0
  79. package/lib/providers/github-copilot.ts +21 -44
  80. package/lib/stores/integrations.test.ts +7 -0
  81. package/lib/stores/integrations.ts +4 -4
  82. package/lib/stores/model-config.test.ts +88 -0
  83. package/lib/stores/model-config.ts +90 -8
  84. package/package.json +1 -1
  85. /package/.next/standalone/.next/static/{e2NV1wk6mWg3SIeELXevg → NUrC9xax1sp1Q70nlFqS6}/_buildManifest.js +0 -0
  86. /package/.next/standalone/.next/static/{e2NV1wk6mWg3SIeELXevg → NUrC9xax1sp1Q70nlFqS6}/_ssgManifest.js +0 -0
@@ -17,6 +17,9 @@ export function withModelConfigMutation<T>(
17
17
  export interface ModelConfigRow {
18
18
  name: string; provider: string; model_id: string;
19
19
  params: string; is_default: number;
20
+ // ADR-0102: 0 hides the config from model selection, routing, and the
21
+ // default fallback without deleting it. Optional for rows built in tests.
22
+ enabled?: number;
20
23
  // ADR forthcoming ??? first-class credentials. NULL until the row is
21
24
  // explicitly bound (or until the auto-migration in lib/db/migrations.ts
22
25
  // lifts an inline api_key into a credential row).
@@ -24,6 +27,16 @@ export interface ModelConfigRow {
24
27
  created_at: string; updated_at: string;
25
28
  }
26
29
 
30
+ export class ModelConfigStateError extends Error {
31
+ constructor(message: string, readonly status: 404 | 409) {
32
+ super(message);
33
+ }
34
+ }
35
+
36
+ export function isModelConfigEnabled(cfg: Pick<ModelConfigRow, "enabled"> | null | undefined): boolean {
37
+ return !!cfg && cfg.enabled !== 0;
38
+ }
39
+
27
40
  // `params` holds the provider API key alongside hyperparameters and is
28
41
  // stored encrypted at rest (ADR-0005). Decrypt before returning rows.
29
42
  function decryptRow<T extends { params: string }>(row: T): T {
@@ -37,6 +50,11 @@ export function listModelConfigs(): ModelConfigRow[] {
37
50
  return rows.map(decryptRow);
38
51
  }
39
52
 
53
+ /** Model configs that may be offered for selection or routing. */
54
+ export function listEnabledModelConfigs(): ModelConfigRow[] {
55
+ return listModelConfigs().filter(isModelConfigEnabled);
56
+ }
57
+
40
58
  export function getModelConfig(name: string): ModelConfigRow | null {
41
59
  const row = (getDb().prepare("SELECT * FROM model_configs WHERE name=?").get(name) as unknown as ModelConfigRow) ?? null;
42
60
  return row ? decryptRow(row) : null;
@@ -44,15 +62,77 @@ export function getModelConfig(name: string): ModelConfigRow | null {
44
62
 
45
63
  export function getDefaultModelConfig(): ModelConfigRow | null {
46
64
  const db = getDb();
47
- const row = (db.prepare("SELECT * FROM model_configs WHERE is_default=1 LIMIT 1").get() as unknown as ModelConfigRow) ?? null;
65
+ const row = (db.prepare("SELECT * FROM model_configs WHERE is_default=1 AND enabled=1 LIMIT 1").get() as unknown as ModelConfigRow) ?? null;
48
66
  if (row) return decryptRow(row);
49
- const fallback = (db.prepare("SELECT * FROM model_configs ORDER BY name ASC LIMIT 1").get() as unknown as ModelConfigRow) ?? null;
67
+ const fallback = (db.prepare("SELECT * FROM model_configs WHERE enabled=1 ORDER BY name ASC LIMIT 1").get() as unknown as ModelConfigRow) ?? null;
50
68
  return fallback ? decryptRow(fallback) : null;
51
69
  }
52
70
 
71
+ /**
72
+ * Resolve the config a run should use: the named config when it exists and is
73
+ * enabled, otherwise the workspace default. A disabled assignment therefore
74
+ * falls back without being cleared, so re-enabling restores it.
75
+ */
76
+ export function resolveRunnableModelConfig(name: string | null | undefined): ModelConfigRow | null {
77
+ const named = name ? getModelConfig(name) : null;
78
+ if (named && isModelConfigEnabled(named)) return named;
79
+ return getDefaultModelConfig();
80
+ }
81
+
82
+ /** Atomically make `name` the only default model config. */
83
+ export function setDefaultModelConfig(name: string): ModelConfigRow {
84
+ return withDbTransaction(() => {
85
+ const db = getDb();
86
+ const target = getModelConfig(name);
87
+ if (!target) throw new ModelConfigStateError(`Model config "${name}" not found`, 404);
88
+ if (!isModelConfigEnabled(target)) {
89
+ throw new ModelConfigStateError(`Model config "${name}" is disabled; enable it before making it the default`, 409);
90
+ }
91
+ const t = now();
92
+ db.prepare("UPDATE model_configs SET is_default=0, updated_at=? WHERE is_default=1 AND name<>?").run(t, name);
93
+ db.prepare("UPDATE model_configs SET is_default=1, updated_at=? WHERE name=?").run(t, name);
94
+ return getModelConfig(name)!;
95
+ });
96
+ }
97
+
98
+ /**
99
+ * Enable or disable a model config. Disabling the default promotes the
100
+ * alphabetically-first other enabled config in the same transaction, and the
101
+ * last enabled config cannot be disabled so runs always have a model.
102
+ */
103
+ export function setModelConfigEnabled(name: string, enabled: boolean): ModelConfigRow {
104
+ return withDbTransaction(() => {
105
+ const db = getDb();
106
+ const target = getModelConfig(name);
107
+ if (!target) throw new ModelConfigStateError(`Model config "${name}" not found`, 404);
108
+ if (isModelConfigEnabled(target) === enabled) return target;
109
+ const t = now();
110
+ if (enabled) {
111
+ db.prepare("UPDATE model_configs SET enabled=1, updated_at=? WHERE name=?").run(t, name);
112
+ return getModelConfig(name)!;
113
+ }
114
+ const next = db.prepare(
115
+ "SELECT name FROM model_configs WHERE enabled=1 AND name<>? ORDER BY name ASC LIMIT 1",
116
+ ).get(name) as { name?: string } | undefined;
117
+ if (!next?.name) {
118
+ throw new ModelConfigStateError("At least one model config must stay enabled", 409);
119
+ }
120
+ db.prepare("UPDATE model_configs SET enabled=0, is_default=0, updated_at=? WHERE name=?").run(t, name);
121
+ if (target.is_default === 1) {
122
+ db.prepare("UPDATE model_configs SET is_default=1, updated_at=? WHERE name=?").run(t, next.name);
123
+ }
124
+ return getModelConfig(name)!;
125
+ });
126
+ }
127
+
128
+ /**
129
+ * Insert or update a model config. `is_default` undefined keeps the current
130
+ * flag (false for new rows); true atomically clears every other default and
131
+ * re-enables the row, because the default must always be selectable.
132
+ */
53
133
  export function upsertModelConfig(
54
134
  name: string, provider: string, model_id: string,
55
- params: Record<string, unknown>, is_default: boolean,
135
+ params: Record<string, unknown>, is_default: boolean | undefined,
56
136
  credential_id: string | null = null,
57
137
  ): ModelConfigRow {
58
138
  return withDbTransaction(() => {
@@ -67,11 +147,13 @@ export function upsertModelConfig(
67
147
  // other field a custom provider relies on (e.g. custom-provider.js's
68
148
  // username/password auth) on every save.
69
149
  const mergedParams = { ...decodeInlineParams(existing), ...params };
150
+ const finalDefault = is_default ?? existing?.is_default === 1;
151
+ const finalEnabled = finalDefault || !existing || isModelConfigEnabled(existing);
70
152
  const db = getDb();
71
- if (is_default) db.prepare("UPDATE model_configs SET is_default=0").run();
153
+ if (finalDefault) db.prepare("UPDATE model_configs SET is_default=0 WHERE name<>?").run(name);
72
154
  db.prepare(
73
- "INSERT OR REPLACE INTO model_configs (name,provider,model_id,params,is_default,credential_id,created_at,updated_at) VALUES (?,?,?,?,?,?,?,?)"
74
- ).run(name, provider, model_id, encrypt(JSON.stringify(mergedParams)), is_default ? 1 : 0, finalCredId, created_at, t);
155
+ "INSERT OR REPLACE INTO model_configs (name,provider,model_id,params,is_default,enabled,credential_id,created_at,updated_at) VALUES (?,?,?,?,?,?,?,?,?)"
156
+ ).run(name, provider, model_id, encrypt(JSON.stringify(mergedParams)), finalDefault ? 1 : 0, finalEnabled ? 1 : 0, finalCredId, created_at, t);
75
157
  return getModelConfig(name)!;
76
158
  });
77
159
  }
@@ -85,10 +167,10 @@ export function deleteModelConfig(name: string): boolean {
85
167
  // Automatically reset any agent pointing to this deleted model config to NULL (automatic / default model)
86
168
  db.prepare("UPDATE agent_configs SET model_config_name = NULL WHERE model_config_name = ?").run(name);
87
169
  // Avoid the "no default" state: if the deleted row was the default and any
88
- // other rows remain, promote the alphabetically-first remaining row so
170
+ // other enabled rows remain, promote the alphabetically-first one so
89
171
  // agents that fall back to the default keep working.
90
172
  if (wasDefault) {
91
- const next = db.prepare("SELECT name FROM model_configs ORDER BY name ASC LIMIT 1").get() as { name?: string } | undefined;
173
+ const next = db.prepare("SELECT name FROM model_configs WHERE enabled=1 ORDER BY name ASC LIMIT 1").get() as { name?: string } | undefined;
92
174
  if (next?.name) db.prepare("UPDATE model_configs SET is_default=1 WHERE name=?").run(next.name);
93
175
  }
94
176
  return true;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@circuitwall/jarela",
3
- "version": "2.2.0",
3
+ "version": "2.2.1",
4
4
  "description": "Jarela — local chat interface for LangGraph agents (multi-provider, single-process, SQLite-backed).",
5
5
  "license": "Apache-2.0",
6
6
  "author": "Andrew Ge Wu",