@dexto/server 1.6.8 → 1.6.10
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/dist/hono/index.d.ts +173 -12
- package/dist/hono/index.d.ts.map +1 -1
- package/dist/hono/routes/llm.cjs +282 -0
- package/dist/hono/routes/llm.d.ts +90 -0
- package/dist/hono/routes/llm.d.ts.map +1 -1
- package/dist/hono/routes/llm.js +295 -2
- package/dist/hono/routes/sessions.cjs +73 -44
- package/dist/hono/routes/sessions.d.ts +71 -0
- package/dist/hono/routes/sessions.d.ts.map +1 -1
- package/dist/hono/routes/sessions.js +78 -45
- package/dist/hono/routes/system-prompt.d.ts +12 -12
- package/dist/hono/schemas/responses.cjs +2 -1
- package/dist/hono/schemas/responses.d.ts +3 -0
- package/dist/hono/schemas/responses.d.ts.map +1 -1
- package/dist/hono/schemas/responses.js +2 -1
- package/package.json +8 -8
|
@@ -584,6 +584,96 @@ export declare function createLlmRouter(getAgent: GetAgentFn): OpenAPIHono<impor
|
|
|
584
584
|
status: 404;
|
|
585
585
|
};
|
|
586
586
|
};
|
|
587
|
+
} & {
|
|
588
|
+
"/llm/model-picker-state": {
|
|
589
|
+
$get: {
|
|
590
|
+
input: {};
|
|
591
|
+
output: {
|
|
592
|
+
custom: {
|
|
593
|
+
model: string;
|
|
594
|
+
provider: "openai" | "openai-compatible" | "anthropic" | "google" | "groq" | "xai" | "cohere" | "minimax" | "glm" | "openrouter" | "litellm" | "glama" | "vertex" | "bedrock" | "local" | "ollama" | "dexto-nova";
|
|
595
|
+
supportedFileTypes: ("image" | "audio" | "pdf")[];
|
|
596
|
+
source: "custom" | "catalog" | "local-installed";
|
|
597
|
+
displayName?: string | undefined;
|
|
598
|
+
}[];
|
|
599
|
+
featured: {
|
|
600
|
+
model: string;
|
|
601
|
+
provider: "openai" | "openai-compatible" | "anthropic" | "google" | "groq" | "xai" | "cohere" | "minimax" | "glm" | "openrouter" | "litellm" | "glama" | "vertex" | "bedrock" | "local" | "ollama" | "dexto-nova";
|
|
602
|
+
supportedFileTypes: ("image" | "audio" | "pdf")[];
|
|
603
|
+
source: "custom" | "catalog" | "local-installed";
|
|
604
|
+
displayName?: string | undefined;
|
|
605
|
+
}[];
|
|
606
|
+
recents: {
|
|
607
|
+
model: string;
|
|
608
|
+
provider: "openai" | "openai-compatible" | "anthropic" | "google" | "groq" | "xai" | "cohere" | "minimax" | "glm" | "openrouter" | "litellm" | "glama" | "vertex" | "bedrock" | "local" | "ollama" | "dexto-nova";
|
|
609
|
+
supportedFileTypes: ("image" | "audio" | "pdf")[];
|
|
610
|
+
source: "custom" | "catalog" | "local-installed";
|
|
611
|
+
displayName?: string | undefined;
|
|
612
|
+
}[];
|
|
613
|
+
favorites: {
|
|
614
|
+
model: string;
|
|
615
|
+
provider: "openai" | "openai-compatible" | "anthropic" | "google" | "groq" | "xai" | "cohere" | "minimax" | "glm" | "openrouter" | "litellm" | "glama" | "vertex" | "bedrock" | "local" | "ollama" | "dexto-nova";
|
|
616
|
+
supportedFileTypes: ("image" | "audio" | "pdf")[];
|
|
617
|
+
source: "custom" | "catalog" | "local-installed";
|
|
618
|
+
displayName?: string | undefined;
|
|
619
|
+
}[];
|
|
620
|
+
};
|
|
621
|
+
outputFormat: "json";
|
|
622
|
+
status: 200;
|
|
623
|
+
};
|
|
624
|
+
};
|
|
625
|
+
} & {
|
|
626
|
+
"/llm/model-picker-state/recents": {
|
|
627
|
+
$post: {
|
|
628
|
+
input: {
|
|
629
|
+
json: {
|
|
630
|
+
model: string;
|
|
631
|
+
provider: "openai" | "openai-compatible" | "anthropic" | "google" | "groq" | "xai" | "cohere" | "minimax" | "glm" | "openrouter" | "litellm" | "glama" | "vertex" | "bedrock" | "local" | "ollama" | "dexto-nova";
|
|
632
|
+
};
|
|
633
|
+
};
|
|
634
|
+
output: {
|
|
635
|
+
ok: true;
|
|
636
|
+
};
|
|
637
|
+
outputFormat: "json";
|
|
638
|
+
status: 200;
|
|
639
|
+
};
|
|
640
|
+
};
|
|
641
|
+
} & {
|
|
642
|
+
"/llm/model-picker-state/favorites/toggle": {
|
|
643
|
+
$post: {
|
|
644
|
+
input: {
|
|
645
|
+
json: {
|
|
646
|
+
model: string;
|
|
647
|
+
provider: "openai" | "openai-compatible" | "anthropic" | "google" | "groq" | "xai" | "cohere" | "minimax" | "glm" | "openrouter" | "litellm" | "glama" | "vertex" | "bedrock" | "local" | "ollama" | "dexto-nova";
|
|
648
|
+
};
|
|
649
|
+
};
|
|
650
|
+
output: {
|
|
651
|
+
ok: true;
|
|
652
|
+
isFavorite: boolean;
|
|
653
|
+
};
|
|
654
|
+
outputFormat: "json";
|
|
655
|
+
status: 200;
|
|
656
|
+
};
|
|
657
|
+
};
|
|
658
|
+
} & {
|
|
659
|
+
"/llm/model-picker-state/favorites": {
|
|
660
|
+
$put: {
|
|
661
|
+
input: {
|
|
662
|
+
json: {
|
|
663
|
+
favorites: {
|
|
664
|
+
model: string;
|
|
665
|
+
provider: "openai" | "openai-compatible" | "anthropic" | "google" | "groq" | "xai" | "cohere" | "minimax" | "glm" | "openrouter" | "litellm" | "glama" | "vertex" | "bedrock" | "local" | "ollama" | "dexto-nova";
|
|
666
|
+
}[];
|
|
667
|
+
};
|
|
668
|
+
};
|
|
669
|
+
output: {
|
|
670
|
+
ok: true;
|
|
671
|
+
count: number;
|
|
672
|
+
};
|
|
673
|
+
outputFormat: "json";
|
|
674
|
+
status: 200;
|
|
675
|
+
};
|
|
676
|
+
};
|
|
587
677
|
} & {
|
|
588
678
|
"/llm/capabilities": {
|
|
589
679
|
$get: {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"llm.d.ts","sourceRoot":"","sources":["../../../src/hono/routes/llm.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAkB,MAAM,mBAAmB,CAAC;AAChE,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;
|
|
1
|
+
{"version":3,"file":"llm.d.ts","sourceRoot":"","sources":["../../../src/hono/routes/llm.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAkB,MAAM,mBAAmB,CAAC;AAChE,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAkC9C,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,MAAM,CAAC;AAQpC,KAAK,UAAU,GAAG,CAAC,GAAG,EAAE,OAAO,KAAK,UAAU,GAAG,OAAO,CAAC,UAAU,CAAC,CAAC;AA0IrE,wBAAgB,eAAe,CAAC,QAAQ,EAAE,UAAU;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;oCAkGhC,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;oCAnKS,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;QA24B9B"}
|
package/dist/hono/routes/llm.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { OpenAPIHono, createRoute, z } from "@hono/zod-openapi";
|
|
2
|
-
import { DextoRuntimeError, ErrorScope, ErrorType } from "@dexto/core";
|
|
2
|
+
import { DextoRuntimeError, ErrorScope, ErrorType, logger } from "@dexto/core";
|
|
3
3
|
import {
|
|
4
4
|
LLM_REGISTRY,
|
|
5
5
|
LLM_PROVIDERS,
|
|
@@ -7,7 +7,9 @@ import {
|
|
|
7
7
|
supportsBaseURL,
|
|
8
8
|
getAllModelsForProvider,
|
|
9
9
|
getCuratedModelsForProvider,
|
|
10
|
+
getCuratedModelRefsForProviders,
|
|
10
11
|
getSupportedFileTypesForModel,
|
|
12
|
+
getLocalModelById,
|
|
11
13
|
getReasoningProfile,
|
|
12
14
|
LLMUpdatesSchema
|
|
13
15
|
} from "@dexto/core";
|
|
@@ -16,14 +18,24 @@ import {
|
|
|
16
18
|
loadCustomModels,
|
|
17
19
|
saveCustomModel,
|
|
18
20
|
deleteCustomModel,
|
|
21
|
+
loadModelPickerState,
|
|
22
|
+
saveModelPickerState,
|
|
23
|
+
recordRecentModel,
|
|
24
|
+
toggleFavoriteModel,
|
|
25
|
+
setFavoriteModels,
|
|
26
|
+
pruneModelPickerState,
|
|
27
|
+
toModelPickerKey,
|
|
28
|
+
getAllInstalledModels,
|
|
19
29
|
CustomModelSchema,
|
|
20
30
|
isDextoAuthEnabled
|
|
21
31
|
} from "@dexto/agent-management";
|
|
22
32
|
import {
|
|
23
33
|
ProviderCatalogSchema,
|
|
24
34
|
ModelFlatSchema,
|
|
25
|
-
LLMConfigResponseSchema
|
|
35
|
+
LLMConfigResponseSchema,
|
|
36
|
+
StandardErrorEnvelopeSchema
|
|
26
37
|
} from "../schemas/responses.js";
|
|
38
|
+
const MODEL_PICKER_FEATURED_LIMIT = 8;
|
|
27
39
|
const CurrentQuerySchema = z.object({
|
|
28
40
|
sessionId: z.string().optional().describe("Session identifier to retrieve session-specific LLM configuration")
|
|
29
41
|
}).strict().describe("Query parameters for getting current LLM configuration");
|
|
@@ -51,6 +63,46 @@ const SwitchLLMBodySchema = LLMUpdatesSchema.and(
|
|
|
51
63
|
sessionId: z.string().optional().describe("Session identifier for session-specific LLM configuration")
|
|
52
64
|
})
|
|
53
65
|
).describe("LLM switch request body with optional session ID and LLM fields");
|
|
66
|
+
const ModelPickerModelRefSchema = z.object({
|
|
67
|
+
provider: z.enum(LLM_PROVIDERS).describe("LLM provider"),
|
|
68
|
+
model: z.string().trim().min(1).describe("Model ID")
|
|
69
|
+
}).strict().describe("Provider/model pair for model picker state operations");
|
|
70
|
+
const ModelPickerEntrySchema = z.object({
|
|
71
|
+
provider: z.enum(LLM_PROVIDERS).describe("LLM provider"),
|
|
72
|
+
model: z.string().describe("Model ID"),
|
|
73
|
+
displayName: z.string().optional().describe("Human-readable model name"),
|
|
74
|
+
supportedFileTypes: z.array(z.enum(SUPPORTED_FILE_TYPES)).describe("File types supported by this model"),
|
|
75
|
+
source: z.enum(["catalog", "custom", "local-installed"]).describe("Where this model comes from")
|
|
76
|
+
}).strict().describe("Hydrated model picker entry");
|
|
77
|
+
const ModelPickerErrorSchema = StandardErrorEnvelopeSchema.describe(
|
|
78
|
+
"Standard error response for model picker endpoints"
|
|
79
|
+
);
|
|
80
|
+
const ModelPickerErrorResponses = {
|
|
81
|
+
400: {
|
|
82
|
+
description: "Validation or request error",
|
|
83
|
+
content: {
|
|
84
|
+
"application/json": {
|
|
85
|
+
schema: ModelPickerErrorSchema
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
},
|
|
89
|
+
404: {
|
|
90
|
+
description: "Resource not found",
|
|
91
|
+
content: {
|
|
92
|
+
"application/json": {
|
|
93
|
+
schema: ModelPickerErrorSchema
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
},
|
|
97
|
+
500: {
|
|
98
|
+
description: "Internal server error",
|
|
99
|
+
content: {
|
|
100
|
+
"application/json": {
|
|
101
|
+
schema: ModelPickerErrorSchema
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
};
|
|
54
106
|
function createLlmRouter(getAgent) {
|
|
55
107
|
const app = new OpenAPIHono();
|
|
56
108
|
const currentRoute = createRoute({
|
|
@@ -284,6 +336,217 @@ function createLlmRouter(getAgent) {
|
|
|
284
336
|
}
|
|
285
337
|
}
|
|
286
338
|
});
|
|
339
|
+
const modelPickerStateRoute = createRoute({
|
|
340
|
+
method: "get",
|
|
341
|
+
path: "/llm/model-picker-state",
|
|
342
|
+
summary: "Model Picker State",
|
|
343
|
+
description: "Returns hydrated Featured, Recents, Favorites, and Custom sections for the model picker.",
|
|
344
|
+
tags: ["llm"],
|
|
345
|
+
responses: {
|
|
346
|
+
200: {
|
|
347
|
+
description: "Hydrated model picker sections",
|
|
348
|
+
content: {
|
|
349
|
+
"application/json": {
|
|
350
|
+
schema: z.object({
|
|
351
|
+
featured: z.array(ModelPickerEntrySchema).describe("Curated featured models"),
|
|
352
|
+
recents: z.array(ModelPickerEntrySchema).describe("Most recently used models"),
|
|
353
|
+
favorites: z.array(ModelPickerEntrySchema).describe("User favorited models"),
|
|
354
|
+
custom: z.array(ModelPickerEntrySchema).describe("User-defined custom models")
|
|
355
|
+
}).strict()
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
},
|
|
359
|
+
...ModelPickerErrorResponses
|
|
360
|
+
}
|
|
361
|
+
});
|
|
362
|
+
const recordRecentModelRoute = createRoute({
|
|
363
|
+
method: "post",
|
|
364
|
+
path: "/llm/model-picker-state/recents",
|
|
365
|
+
summary: "Record Recent Model",
|
|
366
|
+
description: "Records a model selection in recents.",
|
|
367
|
+
tags: ["llm"],
|
|
368
|
+
request: {
|
|
369
|
+
body: {
|
|
370
|
+
required: true,
|
|
371
|
+
content: {
|
|
372
|
+
"application/json": {
|
|
373
|
+
schema: ModelPickerModelRefSchema
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
},
|
|
378
|
+
responses: {
|
|
379
|
+
200: {
|
|
380
|
+
description: "Recent model recorded",
|
|
381
|
+
content: {
|
|
382
|
+
"application/json": {
|
|
383
|
+
schema: z.object({
|
|
384
|
+
ok: z.literal(true).describe("Success indicator")
|
|
385
|
+
}).strict()
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
},
|
|
389
|
+
...ModelPickerErrorResponses
|
|
390
|
+
}
|
|
391
|
+
});
|
|
392
|
+
const toggleFavoriteModelRoute = createRoute({
|
|
393
|
+
method: "post",
|
|
394
|
+
path: "/llm/model-picker-state/favorites/toggle",
|
|
395
|
+
summary: "Toggle Favorite Model",
|
|
396
|
+
description: "Adds or removes a model from favorites.",
|
|
397
|
+
tags: ["llm"],
|
|
398
|
+
request: {
|
|
399
|
+
body: {
|
|
400
|
+
required: true,
|
|
401
|
+
content: {
|
|
402
|
+
"application/json": {
|
|
403
|
+
schema: ModelPickerModelRefSchema
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
},
|
|
408
|
+
responses: {
|
|
409
|
+
200: {
|
|
410
|
+
description: "Favorite toggled",
|
|
411
|
+
content: {
|
|
412
|
+
"application/json": {
|
|
413
|
+
schema: z.object({
|
|
414
|
+
ok: z.literal(true).describe("Success indicator"),
|
|
415
|
+
isFavorite: z.boolean().describe("Whether the model is now favorited")
|
|
416
|
+
}).strict()
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
},
|
|
420
|
+
...ModelPickerErrorResponses
|
|
421
|
+
}
|
|
422
|
+
});
|
|
423
|
+
const setFavoritesRoute = createRoute({
|
|
424
|
+
method: "put",
|
|
425
|
+
path: "/llm/model-picker-state/favorites",
|
|
426
|
+
summary: "Set Favorite Models",
|
|
427
|
+
description: "Replaces favorite models list. Used by migration or bulk updates.",
|
|
428
|
+
tags: ["llm"],
|
|
429
|
+
request: {
|
|
430
|
+
body: {
|
|
431
|
+
required: true,
|
|
432
|
+
content: {
|
|
433
|
+
"application/json": {
|
|
434
|
+
schema: z.object({
|
|
435
|
+
favorites: z.array(ModelPickerModelRefSchema).describe("Complete list of favorite model references")
|
|
436
|
+
}).strict()
|
|
437
|
+
}
|
|
438
|
+
}
|
|
439
|
+
}
|
|
440
|
+
},
|
|
441
|
+
responses: {
|
|
442
|
+
200: {
|
|
443
|
+
description: "Favorites updated",
|
|
444
|
+
content: {
|
|
445
|
+
"application/json": {
|
|
446
|
+
schema: z.object({
|
|
447
|
+
ok: z.literal(true).describe("Success indicator"),
|
|
448
|
+
count: z.number().int().nonnegative().describe("Number of favorites persisted")
|
|
449
|
+
}).strict()
|
|
450
|
+
}
|
|
451
|
+
}
|
|
452
|
+
},
|
|
453
|
+
...ModelPickerErrorResponses
|
|
454
|
+
}
|
|
455
|
+
});
|
|
456
|
+
const isProviderEnabled = (provider) => provider !== "dexto-nova" || isDextoAuthEnabled();
|
|
457
|
+
const dedupeEntries = (entries) => {
|
|
458
|
+
const seen = /* @__PURE__ */ new Set();
|
|
459
|
+
const deduped = [];
|
|
460
|
+
for (const entry of entries) {
|
|
461
|
+
const key = toModelPickerKey(entry);
|
|
462
|
+
if (seen.has(key)) {
|
|
463
|
+
continue;
|
|
464
|
+
}
|
|
465
|
+
seen.add(key);
|
|
466
|
+
deduped.push(entry);
|
|
467
|
+
}
|
|
468
|
+
return deduped;
|
|
469
|
+
};
|
|
470
|
+
const buildModelPickerSections = async () => {
|
|
471
|
+
const byKey = /* @__PURE__ */ new Map();
|
|
472
|
+
const customSection = [];
|
|
473
|
+
for (const provider of LLM_PROVIDERS) {
|
|
474
|
+
if (!isProviderEnabled(provider)) {
|
|
475
|
+
continue;
|
|
476
|
+
}
|
|
477
|
+
const providerInfo = LLM_REGISTRY[provider];
|
|
478
|
+
for (const model of getAllModelsForProvider(provider)) {
|
|
479
|
+
const supportedFileTypes = Array.isArray(model.supportedFileTypes) && model.supportedFileTypes.length > 0 ? model.supportedFileTypes : providerInfo.supportedFileTypes;
|
|
480
|
+
const entry = {
|
|
481
|
+
provider,
|
|
482
|
+
model: model.name,
|
|
483
|
+
displayName: model.displayName || model.name,
|
|
484
|
+
supportedFileTypes,
|
|
485
|
+
source: "catalog"
|
|
486
|
+
};
|
|
487
|
+
const key = toModelPickerKey(entry);
|
|
488
|
+
if (!byKey.has(key)) {
|
|
489
|
+
byKey.set(key, entry);
|
|
490
|
+
}
|
|
491
|
+
}
|
|
492
|
+
}
|
|
493
|
+
const customModels = await loadCustomModels();
|
|
494
|
+
for (const customModel of customModels) {
|
|
495
|
+
const provider = customModel.provider ?? "openai-compatible";
|
|
496
|
+
if (!isProviderEnabled(provider)) {
|
|
497
|
+
continue;
|
|
498
|
+
}
|
|
499
|
+
const providerInfo = LLM_REGISTRY[provider];
|
|
500
|
+
const entry = {
|
|
501
|
+
provider,
|
|
502
|
+
model: customModel.name,
|
|
503
|
+
displayName: customModel.displayName || customModel.name,
|
|
504
|
+
supportedFileTypes: providerInfo?.supportedFileTypes ?? [],
|
|
505
|
+
source: "custom"
|
|
506
|
+
};
|
|
507
|
+
byKey.set(toModelPickerKey(entry), entry);
|
|
508
|
+
customSection.push(entry);
|
|
509
|
+
}
|
|
510
|
+
const localProviderSupportedFileTypes = LLM_REGISTRY.local.supportedFileTypes;
|
|
511
|
+
const installedLocalModels = await getAllInstalledModels();
|
|
512
|
+
for (const installedModel of installedLocalModels) {
|
|
513
|
+
const modelInfo = getLocalModelById(installedModel.id);
|
|
514
|
+
const entry = {
|
|
515
|
+
provider: "local",
|
|
516
|
+
model: installedModel.id,
|
|
517
|
+
displayName: modelInfo?.name || installedModel.id,
|
|
518
|
+
supportedFileTypes: localProviderSupportedFileTypes,
|
|
519
|
+
source: "local-installed"
|
|
520
|
+
};
|
|
521
|
+
byKey.set(toModelPickerKey(entry), entry);
|
|
522
|
+
}
|
|
523
|
+
const featuredProviders = LLM_PROVIDERS.filter((provider) => isProviderEnabled(provider));
|
|
524
|
+
const featured = getCuratedModelRefsForProviders({
|
|
525
|
+
providers: featuredProviders,
|
|
526
|
+
max: MODEL_PICKER_FEATURED_LIMIT
|
|
527
|
+
}).map((ref) => byKey.get(toModelPickerKey(ref))).filter((entry) => Boolean(entry));
|
|
528
|
+
const state = await loadModelPickerState();
|
|
529
|
+
const pruned = pruneModelPickerState({
|
|
530
|
+
state,
|
|
531
|
+
allowedKeys: new Set(byKey.keys())
|
|
532
|
+
});
|
|
533
|
+
const shouldPersistPrunedState = state.recents.length !== pruned.recents.length || state.favorites.length !== pruned.favorites.length;
|
|
534
|
+
if (shouldPersistPrunedState) {
|
|
535
|
+
void saveModelPickerState(pruned).catch((error) => {
|
|
536
|
+
logger.warn(
|
|
537
|
+
`Failed to persist pruned model picker state: ${error instanceof Error ? error.message : String(error)}`
|
|
538
|
+
);
|
|
539
|
+
});
|
|
540
|
+
}
|
|
541
|
+
const recents = pruned.recents.map((entry) => byKey.get(toModelPickerKey(entry))).filter((entry) => Boolean(entry));
|
|
542
|
+
const favorites = pruned.favorites.map((entry) => byKey.get(toModelPickerKey(entry))).filter((entry) => Boolean(entry));
|
|
543
|
+
return {
|
|
544
|
+
featured: dedupeEntries(featured),
|
|
545
|
+
recents,
|
|
546
|
+
favorites,
|
|
547
|
+
custom: dedupeEntries(customSection)
|
|
548
|
+
};
|
|
549
|
+
};
|
|
287
550
|
return app.openapi(currentRoute, async (ctx) => {
|
|
288
551
|
const agent = await getAgent(ctx);
|
|
289
552
|
const { sessionId } = ctx.req.valid("query");
|
|
@@ -405,6 +668,13 @@ function createLlmRouter(getAgent) {
|
|
|
405
668
|
const raw = ctx.req.valid("json");
|
|
406
669
|
const { sessionId, ...llmUpdates } = raw;
|
|
407
670
|
const config = await agent.switchLLM(llmUpdates, sessionId);
|
|
671
|
+
try {
|
|
672
|
+
await recordRecentModel({
|
|
673
|
+
provider: config.provider,
|
|
674
|
+
model: config.model
|
|
675
|
+
});
|
|
676
|
+
} catch {
|
|
677
|
+
}
|
|
408
678
|
const { apiKey, ...configWithoutKey } = config;
|
|
409
679
|
return ctx.json({
|
|
410
680
|
config: {
|
|
@@ -434,6 +704,29 @@ function createLlmRouter(getAgent) {
|
|
|
434
704
|
);
|
|
435
705
|
}
|
|
436
706
|
return ctx.json({ ok: true, deleted: name }, 200);
|
|
707
|
+
}).openapi(modelPickerStateRoute, async (ctx) => {
|
|
708
|
+
const sections = await buildModelPickerSections();
|
|
709
|
+
return ctx.json(sections);
|
|
710
|
+
}).openapi(recordRecentModelRoute, async (ctx) => {
|
|
711
|
+
const modelRef = ctx.req.valid("json");
|
|
712
|
+
await recordRecentModel(modelRef);
|
|
713
|
+
return ctx.json({ ok: true });
|
|
714
|
+
}).openapi(toggleFavoriteModelRoute, async (ctx) => {
|
|
715
|
+
const modelRef = ctx.req.valid("json");
|
|
716
|
+
const result = await toggleFavoriteModel(modelRef);
|
|
717
|
+
return ctx.json({
|
|
718
|
+
ok: true,
|
|
719
|
+
isFavorite: result.isFavorite
|
|
720
|
+
});
|
|
721
|
+
}).openapi(setFavoritesRoute, async (ctx) => {
|
|
722
|
+
const payload = ctx.req.valid("json");
|
|
723
|
+
const state = await setFavoriteModels({
|
|
724
|
+
favorites: payload.favorites
|
|
725
|
+
});
|
|
726
|
+
return ctx.json({
|
|
727
|
+
ok: true,
|
|
728
|
+
count: state.favorites.length
|
|
729
|
+
});
|
|
437
730
|
}).openapi(capabilitiesRoute, (ctx) => {
|
|
438
731
|
const { provider, model } = ctx.req.valid("query");
|
|
439
732
|
let supportedFileTypes;
|
|
@@ -26,6 +26,17 @@ var import_responses = require("../schemas/responses.js");
|
|
|
26
26
|
const CreateSessionSchema = import_zod_openapi.z.object({
|
|
27
27
|
sessionId: import_zod_openapi.z.string().optional().describe("A custom ID for the new session")
|
|
28
28
|
}).describe("Request body for creating a new session");
|
|
29
|
+
function mapSessionMetadata(sessionId, metadata, defaults) {
|
|
30
|
+
return {
|
|
31
|
+
id: sessionId,
|
|
32
|
+
createdAt: metadata?.createdAt ?? defaults?.createdAt ?? null,
|
|
33
|
+
lastActivity: metadata?.lastActivity ?? defaults?.lastActivity ?? null,
|
|
34
|
+
messageCount: metadata?.messageCount ?? defaults?.messageCount ?? 0,
|
|
35
|
+
title: metadata?.title ?? defaults?.title ?? null,
|
|
36
|
+
workspaceId: metadata?.workspaceId ?? defaults?.workspaceId ?? null,
|
|
37
|
+
parentSessionId: metadata?.parentSessionId ?? defaults?.parentSessionId ?? null
|
|
38
|
+
};
|
|
39
|
+
}
|
|
29
40
|
function createSessionsRouter(getAgent) {
|
|
30
41
|
const app = new import_zod_openapi.OpenAPIHono();
|
|
31
42
|
const listRoute = (0, import_zod_openapi.createRoute)({
|
|
@@ -91,6 +102,48 @@ function createSessionsRouter(getAgent) {
|
|
|
91
102
|
}
|
|
92
103
|
}
|
|
93
104
|
});
|
|
105
|
+
const forkRoute = (0, import_zod_openapi.createRoute)({
|
|
106
|
+
method: "post",
|
|
107
|
+
path: "/sessions/{sessionId}/fork",
|
|
108
|
+
summary: "Fork Session",
|
|
109
|
+
description: "Creates a new child session by cloning the specified parent session history and metadata lineage.",
|
|
110
|
+
tags: ["sessions"],
|
|
111
|
+
request: {
|
|
112
|
+
params: import_zod_openapi.z.object({
|
|
113
|
+
sessionId: import_zod_openapi.z.string().describe("Parent session identifier")
|
|
114
|
+
})
|
|
115
|
+
},
|
|
116
|
+
responses: {
|
|
117
|
+
201: {
|
|
118
|
+
description: "Forked session created successfully",
|
|
119
|
+
content: {
|
|
120
|
+
"application/json": {
|
|
121
|
+
schema: import_zod_openapi.z.object({
|
|
122
|
+
session: import_responses.SessionMetadataSchema.describe(
|
|
123
|
+
"Newly created child session metadata"
|
|
124
|
+
)
|
|
125
|
+
}).strict()
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
},
|
|
129
|
+
400: {
|
|
130
|
+
description: "Invalid fork request (for example, max session limit reached)",
|
|
131
|
+
content: {
|
|
132
|
+
"application/json": {
|
|
133
|
+
schema: import_responses.StandardErrorEnvelopeSchema
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
},
|
|
137
|
+
404: {
|
|
138
|
+
description: "Parent session not found",
|
|
139
|
+
content: {
|
|
140
|
+
"application/json": {
|
|
141
|
+
schema: import_responses.StandardErrorEnvelopeSchema
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
});
|
|
94
147
|
const historyRoute = (0, import_zod_openapi.createRoute)({
|
|
95
148
|
method: "get",
|
|
96
149
|
path: "/sessions/{sessionId}/history",
|
|
@@ -274,23 +327,9 @@ function createSessionsRouter(getAgent) {
|
|
|
274
327
|
sessionIds.map(async (id) => {
|
|
275
328
|
try {
|
|
276
329
|
const metadata = await agent.getSessionMetadata(id);
|
|
277
|
-
return
|
|
278
|
-
id,
|
|
279
|
-
createdAt: metadata?.createdAt || null,
|
|
280
|
-
lastActivity: metadata?.lastActivity || null,
|
|
281
|
-
messageCount: metadata?.messageCount || 0,
|
|
282
|
-
title: metadata?.title || null,
|
|
283
|
-
workspaceId: metadata?.workspaceId || null
|
|
284
|
-
};
|
|
330
|
+
return mapSessionMetadata(id, metadata);
|
|
285
331
|
} catch {
|
|
286
|
-
return
|
|
287
|
-
id,
|
|
288
|
-
createdAt: null,
|
|
289
|
-
lastActivity: null,
|
|
290
|
-
messageCount: 0,
|
|
291
|
-
title: null,
|
|
292
|
-
workspaceId: null
|
|
293
|
-
};
|
|
332
|
+
return mapSessionMetadata(id, void 0);
|
|
294
333
|
}
|
|
295
334
|
})
|
|
296
335
|
);
|
|
@@ -302,14 +341,21 @@ function createSessionsRouter(getAgent) {
|
|
|
302
341
|
const metadata = await agent.getSessionMetadata(session.id);
|
|
303
342
|
return ctx.json(
|
|
304
343
|
{
|
|
305
|
-
session: {
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
344
|
+
session: mapSessionMetadata(session.id, metadata, {
|
|
345
|
+
createdAt: Date.now(),
|
|
346
|
+
lastActivity: Date.now()
|
|
347
|
+
})
|
|
348
|
+
},
|
|
349
|
+
201
|
|
350
|
+
);
|
|
351
|
+
}).openapi(forkRoute, async (ctx) => {
|
|
352
|
+
const agent = await getAgent(ctx);
|
|
353
|
+
const { sessionId: parentSessionId } = ctx.req.valid("param");
|
|
354
|
+
const session = await agent.forkSession(parentSessionId);
|
|
355
|
+
const metadata = await agent.getSessionMetadata(session.id);
|
|
356
|
+
return ctx.json(
|
|
357
|
+
{
|
|
358
|
+
session: mapSessionMetadata(session.id, metadata)
|
|
313
359
|
},
|
|
314
360
|
201
|
|
315
361
|
);
|
|
@@ -320,12 +366,7 @@ function createSessionsRouter(getAgent) {
|
|
|
320
366
|
const history = await agent.getSessionHistory(sessionId);
|
|
321
367
|
return ctx.json({
|
|
322
368
|
session: {
|
|
323
|
-
|
|
324
|
-
createdAt: metadata?.createdAt || null,
|
|
325
|
-
lastActivity: metadata?.lastActivity || null,
|
|
326
|
-
messageCount: metadata?.messageCount || 0,
|
|
327
|
-
title: metadata?.title || null,
|
|
328
|
-
workspaceId: metadata?.workspaceId || null,
|
|
369
|
+
...mapSessionMetadata(sessionId, metadata),
|
|
329
370
|
history: history.length
|
|
330
371
|
}
|
|
331
372
|
});
|
|
@@ -386,12 +427,7 @@ function createSessionsRouter(getAgent) {
|
|
|
386
427
|
return ctx.json(
|
|
387
428
|
{
|
|
388
429
|
session: {
|
|
389
|
-
|
|
390
|
-
createdAt: metadata?.createdAt || null,
|
|
391
|
-
lastActivity: metadata?.lastActivity || null,
|
|
392
|
-
messageCount: metadata?.messageCount || 0,
|
|
393
|
-
title: metadata?.title || null,
|
|
394
|
-
workspaceId: metadata?.workspaceId || null,
|
|
430
|
+
...mapSessionMetadata(sessionId, metadata),
|
|
395
431
|
isBusy
|
|
396
432
|
}
|
|
397
433
|
},
|
|
@@ -404,14 +440,7 @@ function createSessionsRouter(getAgent) {
|
|
|
404
440
|
await agent.setSessionTitle(sessionId, title);
|
|
405
441
|
const metadata = await agent.getSessionMetadata(sessionId);
|
|
406
442
|
return ctx.json({
|
|
407
|
-
session: {
|
|
408
|
-
id: sessionId,
|
|
409
|
-
createdAt: metadata?.createdAt || null,
|
|
410
|
-
lastActivity: metadata?.lastActivity || null,
|
|
411
|
-
messageCount: metadata?.messageCount || 0,
|
|
412
|
-
title: metadata?.title || title,
|
|
413
|
-
workspaceId: metadata?.workspaceId || null
|
|
414
|
-
}
|
|
443
|
+
session: mapSessionMetadata(sessionId, metadata, { title })
|
|
415
444
|
});
|
|
416
445
|
}).openapi(generateTitleRoute, async (ctx) => {
|
|
417
446
|
const agent = await getAgent(ctx);
|