@alisaitteke/photoshop-mcp 1.7.21 → 1.7.23
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/README.md +26 -0
- package/dist/analytics/events.d.ts.map +1 -1
- package/dist/analytics/events.js +8 -1
- package/dist/analytics/events.js.map +1 -1
- package/dist/analytics/index.d.ts +2 -2
- package/dist/analytics/index.d.ts.map +1 -1
- package/dist/analytics/index.js +2 -2
- package/dist/analytics/index.js.map +1 -1
- package/dist/analytics/mcp-client.d.ts.map +1 -1
- package/dist/analytics/mcp-client.js +2 -1
- package/dist/analytics/mcp-client.js.map +1 -1
- package/dist/analytics/mcp-session.d.ts +14 -0
- package/dist/analytics/mcp-session.d.ts.map +1 -1
- package/dist/analytics/mcp-session.js +91 -0
- package/dist/analytics/mcp-session.js.map +1 -1
- package/dist/core/server.d.ts.map +1 -1
- package/dist/core/server.js +2 -5
- package/dist/core/server.js.map +1 -1
- package/dist/prompts/templates/composite-blend.js +2 -2
- package/dist/prompts/templates/composite-blend.js.map +1 -1
- package/dist/prompts/templates/dodge-burn-guide.js +1 -1
- package/dist/prompts/templates/dodge-burn-guide.js.map +1 -1
- package/dist/prompts/templates/dodge-burn.js +1 -1
- package/dist/prompts/templates/dodge-burn.js.map +1 -1
- package/dist/prompts/templates/gradient-fade.js +1 -1
- package/dist/prompts/templates/gradient-fade.js.map +1 -1
- package/dist/prompts/templates/sky-blend.js +1 -1
- package/dist/prompts/templates/sky-blend.js.map +1 -1
- package/dist/ui/agent/action-plan.d.ts.map +1 -1
- package/dist/ui/agent/action-plan.js +17 -30
- package/dist/ui/agent/action-plan.js.map +1 -1
- package/dist/ui/agent/api-key.d.ts.map +1 -1
- package/dist/ui/agent/api-key.js +7 -15
- package/dist/ui/agent/api-key.js.map +1 -1
- package/dist/ui/agent/claude-account.d.ts.map +1 -1
- package/dist/ui/agent/claude-account.js +6 -9
- package/dist/ui/agent/claude-account.js.map +1 -1
- package/dist/ui/agent/gemini-account.d.ts.map +1 -1
- package/dist/ui/agent/gemini-account.js +7 -8
- package/dist/ui/agent/gemini-account.js.map +1 -1
- package/dist/ui/agent/instant.d.ts +34 -0
- package/dist/ui/agent/instant.d.ts.map +1 -0
- package/dist/ui/agent/instant.js +78 -0
- package/dist/ui/agent/instant.js.map +1 -0
- package/dist/ui/agent/shared.d.ts +28 -6
- package/dist/ui/agent/shared.d.ts.map +1 -1
- package/dist/ui/agent/shared.js +21 -0
- package/dist/ui/agent/shared.js.map +1 -1
- package/dist/ui/agent.d.ts.map +1 -1
- package/dist/ui/agent.js +32 -4
- package/dist/ui/agent.js.map +1 -1
- package/dist/ui/config.d.ts +14 -0
- package/dist/ui/config.d.ts.map +1 -1
- package/dist/ui/config.js +17 -0
- package/dist/ui/config.js.map +1 -1
- package/dist/ui/intent/catalog.d.ts +39 -0
- package/dist/ui/intent/catalog.d.ts.map +1 -0
- package/dist/ui/intent/catalog.js +160 -0
- package/dist/ui/intent/catalog.js.map +1 -0
- package/dist/ui/intent/router.d.ts +92 -0
- package/dist/ui/intent/router.d.ts.map +1 -0
- package/dist/ui/intent/router.js +146 -0
- package/dist/ui/intent/router.js.map +1 -0
- package/dist/ui/intent/service.d.ts +55 -0
- package/dist/ui/intent/service.d.ts.map +1 -0
- package/dist/ui/intent/service.js +150 -0
- package/dist/ui/intent/service.js.map +1 -0
- package/dist/ui/server.d.ts.map +1 -1
- package/dist/ui/server.js +74 -2
- package/dist/ui/server.js.map +1 -1
- package/dist/ui/store/chats.d.ts +16 -0
- package/dist/ui/store/chats.d.ts.map +1 -1
- package/dist/ui/store/chats.js.map +1 -1
- package/dist/ui/store/previews.d.ts +28 -0
- package/dist/ui/store/previews.d.ts.map +1 -0
- package/dist/ui/store/previews.js +120 -0
- package/dist/ui/store/previews.js.map +1 -0
- package/package.json +2 -2
- package/web/dist/assets/index-hrv691GI.js +288 -0
- package/web/dist/assets/index-tyuEMiXQ.css +1 -0
- package/web/dist/index.html +2 -2
- package/web/dist/assets/index-DVa56xB2.js +0 -263
- package/web/dist/assets/index-vi5C3hqd.css +0 -1
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
import { BLEND_MODES, INSTANT_INTENTS, OTHER_DESCRIPTION, OTHER_INTENT, findIntent, humanBlendMode, } from './catalog.js';
|
|
2
|
+
export const THRESHOLDS = {
|
|
3
|
+
/** Intent confidence needed to skip the LLM. Docs suggest ~0.85 for acting directly. */
|
|
4
|
+
instant: 0.85,
|
|
5
|
+
/** A slot value (number, blend mode) must be at least this sure. */
|
|
6
|
+
slot: 0.7,
|
|
7
|
+
/** P(multi-step) must stay below this for instant. */
|
|
8
|
+
multiStepMax: 0.3,
|
|
9
|
+
/** Below this P(actionable) we ask a question first. */
|
|
10
|
+
clarifyBelow: 0.35,
|
|
11
|
+
/** Above this P(needs visual) the agent loop with previews is used. */
|
|
12
|
+
visualMin: 0.6,
|
|
13
|
+
/** Above this P(multi-step) the Action Plan is used. */
|
|
14
|
+
planMin: 0.5,
|
|
15
|
+
};
|
|
16
|
+
const NONE = 'none';
|
|
17
|
+
/** Candidate numbers for the pre-parsed value pattern: Jev picks one, code parses it. */
|
|
18
|
+
export function extractNumbers(prompt) {
|
|
19
|
+
const found = prompt.match(/\d{1,4}(?:[.,]\d+)?/g) ?? [];
|
|
20
|
+
return [...new Set(found.map((n) => n.replace(',', '.')))].slice(0, 12);
|
|
21
|
+
}
|
|
22
|
+
export function buildQuestions(prompt) {
|
|
23
|
+
const intents = {};
|
|
24
|
+
for (const intent of INSTANT_INTENTS)
|
|
25
|
+
intents[intent.id] = intent.describe;
|
|
26
|
+
intents[OTHER_INTENT] = OTHER_DESCRIPTION;
|
|
27
|
+
const blendModes = {};
|
|
28
|
+
for (const mode of BLEND_MODES)
|
|
29
|
+
blendModes[mode] = humanBlendMode(mode);
|
|
30
|
+
blendModes[NONE] = 'No blend mode is named.';
|
|
31
|
+
const questions = {
|
|
32
|
+
intent: {
|
|
33
|
+
type: 'choice',
|
|
34
|
+
instructions: 'The `request` is what a user typed to control Adobe Photoshop. Which single command is it? Pick `other` if it asks for more than one change or for anything not listed.',
|
|
35
|
+
criteria: intents,
|
|
36
|
+
},
|
|
37
|
+
multi_step: {
|
|
38
|
+
type: 'noul',
|
|
39
|
+
instructions: 'Does the `request` ask for more than one separate edit or operation?',
|
|
40
|
+
},
|
|
41
|
+
needs_visual: {
|
|
42
|
+
type: 'noul',
|
|
43
|
+
instructions: 'To carry out the `request`, would someone have to look at the image first to decide what to change (for example "make it look better" or "fix what looks off")?',
|
|
44
|
+
},
|
|
45
|
+
actionable: {
|
|
46
|
+
type: 'noul',
|
|
47
|
+
instructions: 'Is the `request` specific enough to carry out without asking the user a follow-up question?',
|
|
48
|
+
},
|
|
49
|
+
blend_mode: {
|
|
50
|
+
type: 'choice',
|
|
51
|
+
instructions: 'Which layer blend mode does the `request` name, if any?',
|
|
52
|
+
criteria: blendModes,
|
|
53
|
+
},
|
|
54
|
+
};
|
|
55
|
+
const numbers = extractNumbers(prompt);
|
|
56
|
+
if (numbers.length > 0) {
|
|
57
|
+
const criteria = (instructions) => {
|
|
58
|
+
const options = {};
|
|
59
|
+
for (const n of numbers)
|
|
60
|
+
options[n] = null;
|
|
61
|
+
options[NONE] = 'None of these numbers is that value.';
|
|
62
|
+
return { type: 'choice', instructions, criteria: options };
|
|
63
|
+
};
|
|
64
|
+
questions.opacity_value = criteria('Which of these numbers is the layer opacity the `request` asks for?');
|
|
65
|
+
questions.count_value = criteria('Which of these numbers is how many steps the `request` wants to undo or redo?');
|
|
66
|
+
}
|
|
67
|
+
return questions;
|
|
68
|
+
}
|
|
69
|
+
function choiceOf(answers, key) {
|
|
70
|
+
const a = answers[key];
|
|
71
|
+
return a && typeof a.choice === 'string' && typeof a.confidence === 'number'
|
|
72
|
+
? { choice: a.choice, confidence: a.confidence }
|
|
73
|
+
: null;
|
|
74
|
+
}
|
|
75
|
+
function noulOf(answers, key, fallback) {
|
|
76
|
+
const a = answers[key];
|
|
77
|
+
return a && typeof a.noul === 'number' ? a.noul : fallback;
|
|
78
|
+
}
|
|
79
|
+
function resolveSlots(answers) {
|
|
80
|
+
const slots = {};
|
|
81
|
+
const blend = choiceOf(answers, 'blend_mode');
|
|
82
|
+
if (blend && blend.choice !== NONE && blend.confidence >= THRESHOLDS.slot)
|
|
83
|
+
slots.blendMode = blend.choice;
|
|
84
|
+
const opacity = choiceOf(answers, 'opacity_value');
|
|
85
|
+
if (opacity && opacity.choice !== NONE && opacity.confidence >= THRESHOLDS.slot) {
|
|
86
|
+
const n = Number(opacity.choice);
|
|
87
|
+
if (Number.isFinite(n) && n >= 0 && n <= 100)
|
|
88
|
+
slots.opacity = Math.round(n);
|
|
89
|
+
}
|
|
90
|
+
const count = choiceOf(answers, 'count_value');
|
|
91
|
+
if (count && count.choice !== NONE && count.confidence >= THRESHOLDS.slot) {
|
|
92
|
+
const n = Number(count.choice);
|
|
93
|
+
if (Number.isInteger(n) && n >= 1 && n <= 50)
|
|
94
|
+
slots.count = n;
|
|
95
|
+
}
|
|
96
|
+
return slots;
|
|
97
|
+
}
|
|
98
|
+
/** Pure decision logic: Jev answers in, route out. */
|
|
99
|
+
export function decide(answers, meta) {
|
|
100
|
+
const intentAnswer = choiceOf(answers, 'intent') ?? { choice: OTHER_INTENT, confidence: 0 };
|
|
101
|
+
const signals = {
|
|
102
|
+
multiStep: noulOf(answers, 'multi_step', 0.5),
|
|
103
|
+
needsVisual: noulOf(answers, 'needs_visual', 0),
|
|
104
|
+
actionable: noulOf(answers, 'actionable', 1),
|
|
105
|
+
};
|
|
106
|
+
const base = { intent: intentAnswer.choice, confidence: intentAnswer.confidence, signals, ...meta };
|
|
107
|
+
const intent = findIntent(intentAnswer.choice);
|
|
108
|
+
if (intent && !intent.risky && intentAnswer.confidence >= THRESHOLDS.instant && signals.multiStep < THRESHOLDS.multiStepMax) {
|
|
109
|
+
const slots = resolveSlots(answers);
|
|
110
|
+
const missing = (intent.requires ?? []).filter((slot) => slots[slot] === undefined);
|
|
111
|
+
if (missing.length === 0) {
|
|
112
|
+
const label = intent.describeCall?.(slots) ?? intent.label;
|
|
113
|
+
return {
|
|
114
|
+
...base,
|
|
115
|
+
route: 'instant',
|
|
116
|
+
label,
|
|
117
|
+
call: { tool: intent.tool, args: intent.args(slots), preview: Boolean(intent.preview), label },
|
|
118
|
+
reason: 'One known command; runs directly without a language model call.',
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
if (signals.actionable < THRESHOLDS.clarifyBelow) {
|
|
123
|
+
return { ...base, route: 'clarify', label: 'Ask first', reason: 'Too vague to act on safely; the model asks one question first.' };
|
|
124
|
+
}
|
|
125
|
+
if (signals.needsVisual >= THRESHOLDS.visualMin) {
|
|
126
|
+
return { ...base, route: 'agent', label: 'Look & iterate', reason: 'Needs to look at the image; the model gets previews as it works.' };
|
|
127
|
+
}
|
|
128
|
+
if (intent?.risky) {
|
|
129
|
+
return { ...base, route: 'plan', label: 'Plan', reason: `${intent.label} is hard to undo, so it goes through a plan.` };
|
|
130
|
+
}
|
|
131
|
+
return {
|
|
132
|
+
...base,
|
|
133
|
+
route: 'plan',
|
|
134
|
+
label: 'Plan',
|
|
135
|
+
reason: signals.multiStep >= THRESHOLDS.planMin
|
|
136
|
+
? 'Several operations; one planning call, then the steps run directly.'
|
|
137
|
+
: 'Not a single known command; the model plans it.',
|
|
138
|
+
};
|
|
139
|
+
}
|
|
140
|
+
export async function classifyIntent(prompt, client, options = {}) {
|
|
141
|
+
const now = options.now ?? Date.now;
|
|
142
|
+
const started = now();
|
|
143
|
+
const result = await client.systemOne({ state: { request: prompt.slice(0, 2000) }, questions: buildQuestions(prompt) }, { signal: options.signal, timeout: options.timeoutMs });
|
|
144
|
+
return decide(result.answers, { latencyMs: Math.max(0, now() - started), model: result.model });
|
|
145
|
+
}
|
|
146
|
+
//# sourceMappingURL=router.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"router.js","sourceRoot":"","sources":["../../../src/ui/intent/router.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,WAAW,EACX,eAAe,EACf,iBAAiB,EACjB,YAAY,EACZ,UAAU,EACV,cAAc,GAEf,MAAM,cAAc,CAAC;AAgDtB,MAAM,CAAC,MAAM,UAAU,GAAG;IACxB,wFAAwF;IACxF,OAAO,EAAE,IAAI;IACb,oEAAoE;IACpE,IAAI,EAAE,GAAG;IACT,sDAAsD;IACtD,YAAY,EAAE,GAAG;IACjB,wDAAwD;IACxD,YAAY,EAAE,IAAI;IAClB,uEAAuE;IACvE,SAAS,EAAE,GAAG;IACd,wDAAwD;IACxD,OAAO,EAAE,GAAG;CACJ,CAAC;AAEX,MAAM,IAAI,GAAG,MAAM,CAAC;AAEpB,yFAAyF;AACzF,MAAM,UAAU,cAAc,CAAC,MAAc;IAC3C,MAAM,KAAK,GAAG,MAAM,CAAC,KAAK,CAAC,sBAAsB,CAAC,IAAI,EAAE,CAAC;IACzD,OAAO,CAAC,GAAG,IAAI,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC,GAAG,EAAE,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;AAC1E,CAAC;AAMD,MAAM,UAAU,cAAc,CAAC,MAAc;IAC3C,MAAM,OAAO,GAA2B,EAAE,CAAC;IAC3C,KAAK,MAAM,MAAM,IAAI,eAAe;QAAE,OAAO,CAAC,MAAM,CAAC,EAAE,CAAC,GAAG,MAAM,CAAC,QAAQ,CAAC;IAC3E,OAAO,CAAC,YAAY,CAAC,GAAG,iBAAiB,CAAC;IAE1C,MAAM,UAAU,GAA2B,EAAE,CAAC;IAC9C,KAAK,MAAM,IAAI,IAAI,WAAW;QAAE,UAAU,CAAC,IAAI,CAAC,GAAG,cAAc,CAAC,IAAI,CAAC,CAAC;IACxE,UAAU,CAAC,IAAI,CAAC,GAAG,yBAAyB,CAAC;IAE7C,MAAM,SAAS,GAA6B;QAC1C,MAAM,EAAE;YACN,IAAI,EAAE,QAAQ;YACd,YAAY,EACV,yKAAyK;YAC3K,QAAQ,EAAE,OAAO;SAClB;QACD,UAAU,EAAE;YACV,IAAI,EAAE,MAAM;YACZ,YAAY,EAAE,sEAAsE;SACrF;QACD,YAAY,EAAE;YACZ,IAAI,EAAE,MAAM;YACZ,YAAY,EACV,iKAAiK;SACpK;QACD,UAAU,EAAE;YACV,IAAI,EAAE,MAAM;YACZ,YAAY,EACV,6FAA6F;SAChG;QACD,UAAU,EAAE;YACV,IAAI,EAAE,QAAQ;YACd,YAAY,EAAE,yDAAyD;YACvE,QAAQ,EAAE,UAAU;SACrB;KACF,CAAC;IAEF,MAAM,OAAO,GAAG,cAAc,CAAC,MAAM,CAAC,CAAC;IACvC,IAAI,OAAO,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QACvB,MAAM,QAAQ,GAAG,CAAC,YAAoB,EAAY,EAAE;YAClD,MAAM,OAAO,GAAkC,EAAE,CAAC;YAClD,KAAK,MAAM,CAAC,IAAI,OAAO;gBAAE,OAAO,CAAC,CAAC,CAAC,GAAG,IAAI,CAAC;YAC3C,OAAO,CAAC,IAAI,CAAC,GAAG,sCAAsC,CAAC;YACvD,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAE,YAAY,EAAE,QAAQ,EAAE,OAAO,EAAE,CAAC;QAC7D,CAAC,CAAC;QACF,SAAS,CAAC,aAAa,GAAG,QAAQ,CAAC,qEAAqE,CAAC,CAAC;QAC1G,SAAS,CAAC,WAAW,GAAG,QAAQ,CAAC,+EAA+E,CAAC,CAAC;IACpH,CAAC;IACD,OAAO,SAAS,CAAC;AACnB,CAAC;AAUD,SAAS,QAAQ,CAAC,OAAgC,EAAE,GAAW;IAC7D,MAAM,CAAC,GAAG,OAAO,CAAC,GAAG,CAAsC,CAAC;IAC5D,OAAO,CAAC,IAAI,OAAO,CAAC,CAAC,MAAM,KAAK,QAAQ,IAAI,OAAO,CAAC,CAAC,UAAU,KAAK,QAAQ;QAC1E,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,CAAC,MAAM,EAAE,UAAU,EAAE,CAAC,CAAC,UAAU,EAAE;QAChD,CAAC,CAAC,IAAI,CAAC;AACX,CAAC;AAED,SAAS,MAAM,CAAC,OAAgC,EAAE,GAAW,EAAE,QAAgB;IAC7E,MAAM,CAAC,GAAG,OAAO,CAAC,GAAG,CAAoC,CAAC;IAC1D,OAAO,CAAC,IAAI,OAAO,CAAC,CAAC,IAAI,KAAK,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,QAAQ,CAAC;AAC7D,CAAC;AAED,SAAS,YAAY,CAAC,OAAgC;IACpD,MAAM,KAAK,GAAe,EAAE,CAAC;IAC7B,MAAM,KAAK,GAAG,QAAQ,CAAC,OAAO,EAAE,YAAY,CAAC,CAAC;IAC9C,IAAI,KAAK,IAAI,KAAK,CAAC,MAAM,KAAK,IAAI,IAAI,KAAK,CAAC,UAAU,IAAI,UAAU,CAAC,IAAI;QAAE,KAAK,CAAC,SAAS,GAAG,KAAK,CAAC,MAAM,CAAC;IAC1G,MAAM,OAAO,GAAG,QAAQ,CAAC,OAAO,EAAE,eAAe,CAAC,CAAC;IACnD,IAAI,OAAO,IAAI,OAAO,CAAC,MAAM,KAAK,IAAI,IAAI,OAAO,CAAC,UAAU,IAAI,UAAU,CAAC,IAAI,EAAE,CAAC;QAChF,MAAM,CAAC,GAAG,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC;QACjC,IAAI,MAAM,CAAC,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,GAAG;YAAE,KAAK,CAAC,OAAO,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;IAC9E,CAAC;IACD,MAAM,KAAK,GAAG,QAAQ,CAAC,OAAO,EAAE,aAAa,CAAC,CAAC;IAC/C,IAAI,KAAK,IAAI,KAAK,CAAC,MAAM,KAAK,IAAI,IAAI,KAAK,CAAC,UAAU,IAAI,UAAU,CAAC,IAAI,EAAE,CAAC;QAC1E,MAAM,CAAC,GAAG,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC;QAC/B,IAAI,MAAM,CAAC,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE;YAAE,KAAK,CAAC,KAAK,GAAG,CAAC,CAAC;IAChE,CAAC;IACD,OAAO,KAAK,CAAC;AACf,CAAC;AAED,sDAAsD;AACtD,MAAM,UAAU,MAAM,CACpB,OAAgC,EAChC,IAA0C;IAE1C,MAAM,YAAY,GAAG,QAAQ,CAAC,OAAO,EAAE,QAAQ,CAAC,IAAI,EAAE,MAAM,EAAE,YAAY,EAAE,UAAU,EAAE,CAAC,EAAE,CAAC;IAC5F,MAAM,OAAO,GAAG;QACd,SAAS,EAAE,MAAM,CAAC,OAAO,EAAE,YAAY,EAAE,GAAG,CAAC;QAC7C,WAAW,EAAE,MAAM,CAAC,OAAO,EAAE,cAAc,EAAE,CAAC,CAAC;QAC/C,UAAU,EAAE,MAAM,CAAC,OAAO,EAAE,YAAY,EAAE,CAAC,CAAC;KAC7C,CAAC;IACF,MAAM,IAAI,GAAG,EAAE,MAAM,EAAE,YAAY,CAAC,MAAM,EAAE,UAAU,EAAE,YAAY,CAAC,UAAU,EAAE,OAAO,EAAE,GAAG,IAAI,EAAE,CAAC;IACpG,MAAM,MAAM,GAAG,UAAU,CAAC,YAAY,CAAC,MAAM,CAAC,CAAC;IAE/C,IAAI,MAAM,IAAI,CAAC,MAAM,CAAC,KAAK,IAAI,YAAY,CAAC,UAAU,IAAI,UAAU,CAAC,OAAO,IAAI,OAAO,CAAC,SAAS,GAAG,UAAU,CAAC,YAAY,EAAE,CAAC;QAC5H,MAAM,KAAK,GAAG,YAAY,CAAC,OAAO,CAAC,CAAC;QACpC,MAAM,OAAO,GAAG,CAAC,MAAM,CAAC,QAAQ,IAAI,EAAE,CAAC,CAAC,MAAM,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,KAAK,CAAC,IAAI,CAAC,KAAK,SAAS,CAAC,CAAC;QACpF,IAAI,OAAO,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YACzB,MAAM,KAAK,GAAG,MAAM,CAAC,YAAY,EAAE,CAAC,KAAK,CAAC,IAAI,MAAM,CAAC,KAAK,CAAC;YAC3D,OAAO;gBACL,GAAG,IAAI;gBACP,KAAK,EAAE,SAAS;gBAChB,KAAK;gBACL,IAAI,EAAE,EAAE,IAAI,EAAE,MAAM,CAAC,IAAI,EAAE,IAAI,EAAE,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,EAAE,OAAO,EAAE,OAAO,CAAC,MAAM,CAAC,OAAO,CAAC,EAAE,KAAK,EAAE;gBAC9F,MAAM,EAAE,iEAAiE;aAC1E,CAAC;QACJ,CAAC;IACH,CAAC;IAED,IAAI,OAAO,CAAC,UAAU,GAAG,UAAU,CAAC,YAAY,EAAE,CAAC;QACjD,OAAO,EAAE,GAAG,IAAI,EAAE,KAAK,EAAE,SAAS,EAAE,KAAK,EAAE,WAAW,EAAE,MAAM,EAAE,gEAAgE,EAAE,CAAC;IACrI,CAAC;IACD,IAAI,OAAO,CAAC,WAAW,IAAI,UAAU,CAAC,SAAS,EAAE,CAAC;QAChD,OAAO,EAAE,GAAG,IAAI,EAAE,KAAK,EAAE,OAAO,EAAE,KAAK,EAAE,gBAAgB,EAAE,MAAM,EAAE,kEAAkE,EAAE,CAAC;IAC1I,CAAC;IACD,IAAI,MAAM,EAAE,KAAK,EAAE,CAAC;QAClB,OAAO,EAAE,GAAG,IAAI,EAAE,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,GAAG,MAAM,CAAC,KAAK,8CAA8C,EAAE,CAAC;IAC1H,CAAC;IACD,OAAO;QACL,GAAG,IAAI;QACP,KAAK,EAAE,MAAM;QACb,KAAK,EAAE,MAAM;QACb,MAAM,EACJ,OAAO,CAAC,SAAS,IAAI,UAAU,CAAC,OAAO;YACrC,CAAC,CAAC,qEAAqE;YACvE,CAAC,CAAC,iDAAiD;KACxD,CAAC;AACJ,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,cAAc,CAClC,MAAc,EACd,MAAuB,EACvB,UAA4E,EAAE;IAE9E,MAAM,GAAG,GAAG,OAAO,CAAC,GAAG,IAAI,IAAI,CAAC,GAAG,CAAC;IACpC,MAAM,OAAO,GAAG,GAAG,EAAE,CAAC;IACtB,MAAM,MAAM,GAAG,MAAM,MAAM,CAAC,SAAS,CACnC,EAAE,KAAK,EAAE,EAAE,OAAO,EAAE,MAAM,CAAC,KAAK,CAAC,CAAC,EAAE,IAAI,CAAC,EAAE,EAAE,SAAS,EAAE,cAAc,CAAC,MAAM,CAAC,EAAE,EAChF,EAAE,MAAM,EAAE,OAAO,CAAC,MAAM,EAAE,OAAO,EAAE,OAAO,CAAC,SAAS,EAAE,CACvD,CAAC;IACF,OAAO,MAAM,CAAC,MAAM,CAAC,OAAO,EAAE,EAAE,SAAS,EAAE,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,GAAG,EAAE,GAAG,OAAO,CAAC,EAAE,KAAK,EAAE,MAAM,CAAC,KAAK,EAAE,CAAC,CAAC;AAClG,CAAC"}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { type IntentRouterConfig } from '../config.js';
|
|
2
|
+
import { type IntentDecision, type IntentRoute } from './router.js';
|
|
3
|
+
type Env = Record<string, string | undefined>;
|
|
4
|
+
export type KeySource = 'settings' | 'env' | null;
|
|
5
|
+
export interface IntentRouterStatus {
|
|
6
|
+
/** Routing will run for the next message. */
|
|
7
|
+
active: boolean;
|
|
8
|
+
enabled: boolean;
|
|
9
|
+
instant: boolean;
|
|
10
|
+
hasApiKey: boolean;
|
|
11
|
+
apiKeyMasked: string | null;
|
|
12
|
+
source: KeySource;
|
|
13
|
+
/** PSMCP_INTENT_ROUTER=off forces routing off regardless of settings. */
|
|
14
|
+
disabledByEnv: boolean;
|
|
15
|
+
}
|
|
16
|
+
export declare function resolveApiKey(cfg: IntentRouterConfig, env: Env): {
|
|
17
|
+
key?: string;
|
|
18
|
+
source: KeySource;
|
|
19
|
+
};
|
|
20
|
+
export declare function computeStatus(cfg: IntentRouterConfig, env: Env): IntentRouterStatus;
|
|
21
|
+
export declare function getIntentRouterStatus(): IntentRouterStatus;
|
|
22
|
+
export declare function isIntentRouterEnabled(): boolean;
|
|
23
|
+
/** With instant commands switched off, a would-be instant route goes through a plan. */
|
|
24
|
+
export declare function applyInstantSetting(decision: IntentDecision, instantAllowed: boolean): IntentDecision;
|
|
25
|
+
/** Drop the cached client and decisions, e.g. after the key or settings change. */
|
|
26
|
+
export declare function resetIntentRouter(): void;
|
|
27
|
+
/**
|
|
28
|
+
* Classify a prompt. Results are cached briefly so the decision shown while
|
|
29
|
+
* typing is reused when the message is sent. Returns null when routing is off.
|
|
30
|
+
*/
|
|
31
|
+
export declare function routeIntent(prompt: string, signal?: AbortSignal): Promise<{
|
|
32
|
+
decision: IntentDecision;
|
|
33
|
+
cached: boolean;
|
|
34
|
+
} | null>;
|
|
35
|
+
export interface KeyCheckResult {
|
|
36
|
+
ok: boolean;
|
|
37
|
+
latencyMs?: number;
|
|
38
|
+
model?: string;
|
|
39
|
+
error?: string;
|
|
40
|
+
}
|
|
41
|
+
/** One tiny request (a single yes/no question) to prove the key works and measure latency. */
|
|
42
|
+
export declare function checkTypeSafeKey(apiKey?: string): Promise<KeyCheckResult>;
|
|
43
|
+
/** What the browser sees: no tool arguments, just the decision. */
|
|
44
|
+
export interface RouteView {
|
|
45
|
+
route: IntentRoute;
|
|
46
|
+
label: string;
|
|
47
|
+
intent: string;
|
|
48
|
+
confidence: number;
|
|
49
|
+
latencyMs: number;
|
|
50
|
+
model: string;
|
|
51
|
+
reason: string;
|
|
52
|
+
}
|
|
53
|
+
export declare function toRouteView(decision: IntentDecision): RouteView;
|
|
54
|
+
export {};
|
|
55
|
+
//# sourceMappingURL=service.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"service.d.ts","sourceRoot":"","sources":["../../../src/ui/intent/service.ts"],"names":[],"mappings":"AAAA,OAAO,EAAqC,KAAK,kBAAkB,EAAE,MAAM,cAAc,CAAC;AAC1F,OAAO,EAAkB,KAAK,cAAc,EAAE,KAAK,WAAW,EAAwB,MAAM,aAAa,CAAC;AAiB1G,KAAK,GAAG,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC,CAAC;AAE9C,MAAM,MAAM,SAAS,GAAG,UAAU,GAAG,KAAK,GAAG,IAAI,CAAC;AAElD,MAAM,WAAW,kBAAkB;IACjC,6CAA6C;IAC7C,MAAM,EAAE,OAAO,CAAC;IAChB,OAAO,EAAE,OAAO,CAAC;IACjB,OAAO,EAAE,OAAO,CAAC;IACjB,SAAS,EAAE,OAAO,CAAC;IACnB,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,MAAM,EAAE,SAAS,CAAC;IAClB,yEAAyE;IACzE,aAAa,EAAE,OAAO,CAAC;CACxB;AAED,wBAAgB,aAAa,CAAC,GAAG,EAAE,kBAAkB,EAAE,GAAG,EAAE,GAAG,GAAG;IAAE,GAAG,CAAC,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,SAAS,CAAA;CAAE,CAMpG;AAED,wBAAgB,aAAa,CAAC,GAAG,EAAE,kBAAkB,EAAE,GAAG,EAAE,GAAG,GAAG,kBAAkB,CAcnF;AAED,wBAAgB,qBAAqB,IAAI,kBAAkB,CAE1D;AAED,wBAAgB,qBAAqB,IAAI,OAAO,CAE/C;AAED,wFAAwF;AACxF,wBAAgB,mBAAmB,CAAC,QAAQ,EAAE,cAAc,EAAE,cAAc,EAAE,OAAO,GAAG,cAAc,CAUrG;AAkBD,mFAAmF;AACnF,wBAAgB,iBAAiB,IAAI,IAAI,CAGxC;AAMD;;;GAGG;AACH,wBAAsB,WAAW,CAC/B,MAAM,EAAE,MAAM,EACd,MAAM,CAAC,EAAE,WAAW,GACnB,OAAO,CAAC;IAAE,QAAQ,EAAE,cAAc,CAAC;IAAC,MAAM,EAAE,OAAO,CAAA;CAAE,GAAG,IAAI,CAAC,CAoB/D;AAED,MAAM,WAAW,cAAc;IAC7B,EAAE,EAAE,OAAO,CAAC;IACZ,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAYD,8FAA8F;AAC9F,wBAAsB,gBAAgB,CAAC,MAAM,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,cAAc,CAAC,CAc/E;AAED,mEAAmE;AACnE,MAAM,WAAW,SAAS;IACxB,KAAK,EAAE,WAAW,CAAC;IACnB,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;IACf,UAAU,EAAE,MAAM,CAAC;IACnB,SAAS,EAAE,MAAM,CAAC;IAClB,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,wBAAgB,WAAW,CAAC,QAAQ,EAAE,cAAc,GAAG,SAAS,CAU/D"}
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
import { getIntentRouterConfig, maskApiKey } from '../config.js';
|
|
2
|
+
import { classifyIntent } from './router.js';
|
|
3
|
+
/**
|
|
4
|
+
* Jev routing is opt-in. The key is set in Settings → Routing (stored with the
|
|
5
|
+
* other provider keys in ~/.photoshop-mcp/data.db); TYPESAFE_API_KEY is only a
|
|
6
|
+
* fallback for headless setups. With no key, the UI behaves exactly as before.
|
|
7
|
+
* When routing is on, prompts are also sent to api.typesafe.ai.
|
|
8
|
+
*/
|
|
9
|
+
const REQUEST_TIMEOUT_MS = 2500;
|
|
10
|
+
const VALIDATE_TIMEOUT_MS = 6000;
|
|
11
|
+
const CACHE_TTL_MS = 5 * 60_000;
|
|
12
|
+
const CACHE_MAX = 100;
|
|
13
|
+
const cache = new Map();
|
|
14
|
+
let client = null;
|
|
15
|
+
export function resolveApiKey(cfg, env) {
|
|
16
|
+
const fromSettings = cfg.apiKey?.trim();
|
|
17
|
+
if (fromSettings)
|
|
18
|
+
return { key: fromSettings, source: 'settings' };
|
|
19
|
+
const fromEnv = env.TYPESAFE_API_KEY?.trim();
|
|
20
|
+
if (fromEnv)
|
|
21
|
+
return { key: fromEnv, source: 'env' };
|
|
22
|
+
return { source: null };
|
|
23
|
+
}
|
|
24
|
+
export function computeStatus(cfg, env) {
|
|
25
|
+
const { key, source } = resolveApiKey(cfg, env);
|
|
26
|
+
const flag = env.PSMCP_INTENT_ROUTER?.trim().toLowerCase();
|
|
27
|
+
const disabledByEnv = flag === 'off' || flag === '0' || flag === 'false';
|
|
28
|
+
const enabled = cfg.enabled !== false;
|
|
29
|
+
return {
|
|
30
|
+
active: Boolean(key) && enabled && !disabledByEnv,
|
|
31
|
+
enabled,
|
|
32
|
+
instant: cfg.instant !== false,
|
|
33
|
+
hasApiKey: Boolean(key),
|
|
34
|
+
apiKeyMasked: maskApiKey(key),
|
|
35
|
+
source,
|
|
36
|
+
disabledByEnv,
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
export function getIntentRouterStatus() {
|
|
40
|
+
return computeStatus(getIntentRouterConfig(), process.env);
|
|
41
|
+
}
|
|
42
|
+
export function isIntentRouterEnabled() {
|
|
43
|
+
return getIntentRouterStatus().active;
|
|
44
|
+
}
|
|
45
|
+
/** With instant commands switched off, a would-be instant route goes through a plan. */
|
|
46
|
+
export function applyInstantSetting(decision, instantAllowed) {
|
|
47
|
+
if (instantAllowed || decision.route !== 'instant')
|
|
48
|
+
return decision;
|
|
49
|
+
const rest = { ...decision };
|
|
50
|
+
delete rest.call;
|
|
51
|
+
return {
|
|
52
|
+
...rest,
|
|
53
|
+
route: 'plan',
|
|
54
|
+
label: 'Plan',
|
|
55
|
+
reason: 'Instant commands are off in Settings, so the model plans it.',
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
async function createClient(apiKey, timeout, maxRetries) {
|
|
59
|
+
const { TypeSafeClient } = await import('@typesafe-ai/sdk');
|
|
60
|
+
return new TypeSafeClient({ apiKey, timeout, retry: { maxRetries }, logLevel: 'off' });
|
|
61
|
+
}
|
|
62
|
+
function getClient(apiKey) {
|
|
63
|
+
if (!client || client.key !== apiKey) {
|
|
64
|
+
const promise = createClient(apiKey, REQUEST_TIMEOUT_MS, 1);
|
|
65
|
+
promise.catch(() => {
|
|
66
|
+
if (client?.promise === promise)
|
|
67
|
+
client = null;
|
|
68
|
+
});
|
|
69
|
+
client = { key: apiKey, promise };
|
|
70
|
+
}
|
|
71
|
+
return client.promise;
|
|
72
|
+
}
|
|
73
|
+
/** Drop the cached client and decisions, e.g. after the key or settings change. */
|
|
74
|
+
export function resetIntentRouter() {
|
|
75
|
+
client = null;
|
|
76
|
+
cache.clear();
|
|
77
|
+
}
|
|
78
|
+
function cacheKey(prompt) {
|
|
79
|
+
return prompt.trim().replace(/\s+/g, ' ').toLowerCase();
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* Classify a prompt. Results are cached briefly so the decision shown while
|
|
83
|
+
* typing is reused when the message is sent. Returns null when routing is off.
|
|
84
|
+
*/
|
|
85
|
+
export async function routeIntent(prompt, signal) {
|
|
86
|
+
const cfg = getIntentRouterConfig();
|
|
87
|
+
const status = computeStatus(cfg, process.env);
|
|
88
|
+
if (!status.active)
|
|
89
|
+
return null;
|
|
90
|
+
const { key } = resolveApiKey(cfg, process.env);
|
|
91
|
+
const normalized = cacheKey(prompt);
|
|
92
|
+
if (!key || !normalized)
|
|
93
|
+
return null;
|
|
94
|
+
const hit = cache.get(normalized);
|
|
95
|
+
if (hit && Date.now() - hit.at < CACHE_TTL_MS) {
|
|
96
|
+
return { decision: applyInstantSetting(hit.decision, status.instant), cached: true };
|
|
97
|
+
}
|
|
98
|
+
const decision = await classifyIntent(prompt, await getClient(key), { signal, timeoutMs: REQUEST_TIMEOUT_MS });
|
|
99
|
+
cache.set(normalized, { at: Date.now(), decision });
|
|
100
|
+
if (cache.size > CACHE_MAX) {
|
|
101
|
+
const oldest = cache.keys().next().value;
|
|
102
|
+
if (oldest !== undefined)
|
|
103
|
+
cache.delete(oldest);
|
|
104
|
+
}
|
|
105
|
+
return { decision: applyInstantSetting(decision, status.instant), cached: false };
|
|
106
|
+
}
|
|
107
|
+
function describeKeyError(err) {
|
|
108
|
+
const e = err;
|
|
109
|
+
if (e?.name === 'AuthenticationError' || e?.status === 401)
|
|
110
|
+
return 'The key was rejected. Check it in console.typesafe.ai.';
|
|
111
|
+
if (e?.name === 'PermissionDeniedError' || e?.status === 403)
|
|
112
|
+
return 'This key has no access to Jev yet (early access).';
|
|
113
|
+
if (e?.name === 'RateLimitError' || e?.status === 429)
|
|
114
|
+
return 'Rate limited by TypeSafe. Try again in a moment.';
|
|
115
|
+
if (e?.name === 'APITimeoutError')
|
|
116
|
+
return 'TypeSafe did not answer in time.';
|
|
117
|
+
if (e?.name === 'APIConnectionError')
|
|
118
|
+
return 'Could not reach api.typesafe.ai.';
|
|
119
|
+
return e?.message || 'Could not verify the key.';
|
|
120
|
+
}
|
|
121
|
+
/** One tiny request (a single yes/no question) to prove the key works and measure latency. */
|
|
122
|
+
export async function checkTypeSafeKey(apiKey) {
|
|
123
|
+
const key = apiKey?.trim() || resolveApiKey(getIntentRouterConfig(), process.env).key;
|
|
124
|
+
if (!key)
|
|
125
|
+
return { ok: false, error: 'No key to check.' };
|
|
126
|
+
try {
|
|
127
|
+
const probe = await createClient(key, VALIDATE_TIMEOUT_MS, 0);
|
|
128
|
+
const started = Date.now();
|
|
129
|
+
const result = await probe.systemOne({
|
|
130
|
+
state: { request: 'undo' },
|
|
131
|
+
questions: { ping: { type: 'noul', instructions: 'Is the `request` about Photoshop?' } },
|
|
132
|
+
});
|
|
133
|
+
return { ok: true, latencyMs: Date.now() - started, model: result.model };
|
|
134
|
+
}
|
|
135
|
+
catch (err) {
|
|
136
|
+
return { ok: false, error: describeKeyError(err) };
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
export function toRouteView(decision) {
|
|
140
|
+
return {
|
|
141
|
+
route: decision.route,
|
|
142
|
+
label: decision.label,
|
|
143
|
+
intent: decision.intent,
|
|
144
|
+
confidence: decision.confidence,
|
|
145
|
+
latencyMs: decision.latencyMs,
|
|
146
|
+
model: decision.model,
|
|
147
|
+
reason: decision.reason,
|
|
148
|
+
};
|
|
149
|
+
}
|
|
150
|
+
//# sourceMappingURL=service.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"service.js","sourceRoot":"","sources":["../../../src/ui/intent/service.ts"],"names":[],"mappings":"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"}
|
package/dist/ui/server.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../../src/ui/server.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../../src/ui/server.ts"],"names":[],"mappings":"AA8FA,MAAM,WAAW,eAAe;IAC9B,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,kEAAkE;IAClE,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,QAAQ;IACvB,GAAG,EAAE,MAAM,CAAC;IACZ,yDAAyD;IACzD,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;CACxB;AAED,wBAAsB,aAAa,CAAC,IAAI,EAAE,eAAe,GAAG,OAAO,CAAC,QAAQ,CAAC,CAwpB5E"}
|
package/dist/ui/server.js
CHANGED
|
@@ -7,8 +7,10 @@ import { fileURLToPath } from 'node:url';
|
|
|
7
7
|
import { capture, captureBetaChatTurn, ensureAnalyticsIdentity, getAnalyticsRuntimeConfig, identifyUiModelSelection, resetAnalyticsProvider, setBetaTelemetryChoice, shutdownAnalytics, } from '../analytics/index.js';
|
|
8
8
|
import { setAnalyticsOptOut } from '../analytics/identity.js';
|
|
9
9
|
import { Logger } from '../utils/logger.js';
|
|
10
|
+
import { deletePreviewImages, readPreviewImage } from './store/previews.js';
|
|
11
|
+
import { checkTypeSafeKey, getIntentRouterStatus, isIntentRouterEnabled, resetIntentRouter, routeIntent, toRouteView, } from './intent/service.js';
|
|
10
12
|
import { buildHistory, runChat, } from './agent.js';
|
|
11
|
-
import { deleteCustomProvider, getAuthMethod, getCustomProvider, getProviderConfig, isProviderConfigAuthenticated, loadConfig, maskApiKey, saveConfig, saveCustomProvider, setProviderConfig, } from './config.js';
|
|
13
|
+
import { deleteCustomProvider, getAuthMethod, getCustomProvider, getProviderConfig, isProviderConfigAuthenticated, loadConfig, maskApiKey, saveConfig, saveCustomProvider, setIntentRouterConfig, setProviderConfig, } from './config.js';
|
|
12
14
|
import { getProvider, listProviders } from './providers/registry.js';
|
|
13
15
|
import { createSessionToken, extractRequestToken, matchesSessionToken, removeSessionFile, SESSION_TOKEN_HEADER, writeSessionFile, } from './security/session-token.js';
|
|
14
16
|
import { appendMessage, createChat, deleteChat, getChat, getMessages, listChats, renameChat, updateChatModel, } from './store/chats.js';
|
|
@@ -118,8 +120,59 @@ export async function startUIServer(opts) {
|
|
|
118
120
|
apiKeyMasked: maskApiKey(active?.apiKey),
|
|
119
121
|
accountLabel: active?.cliAccountLabel ?? null,
|
|
120
122
|
actionPlanBeta: Boolean(config.actionPlanBeta),
|
|
123
|
+
intentRouter: { enabled: isIntentRouterEnabled() },
|
|
121
124
|
});
|
|
122
125
|
});
|
|
126
|
+
// ---- Jev intent routing settings (Settings → Routing) --------------------
|
|
127
|
+
app.get('/api/intent-router', (c) => c.json(getIntentRouterStatus()));
|
|
128
|
+
app.post('/api/intent-router/validate', async (c) => {
|
|
129
|
+
const body = (await c.req.json().catch(() => ({})));
|
|
130
|
+
return c.json(await checkTypeSafeKey(typeof body.apiKey === 'string' ? body.apiKey : undefined));
|
|
131
|
+
});
|
|
132
|
+
app.post('/api/intent-router/key', async (c) => {
|
|
133
|
+
const body = (await c.req.json().catch(() => ({})));
|
|
134
|
+
const apiKey = typeof body.apiKey === 'string' ? body.apiKey.trim() : '';
|
|
135
|
+
if (!apiKey)
|
|
136
|
+
return c.json({ error: 'missing_api_key' }, 400);
|
|
137
|
+
// Saving a key means "use it": switch Auto route back on.
|
|
138
|
+
setIntentRouterConfig({ apiKey, enabled: true });
|
|
139
|
+
resetIntentRouter();
|
|
140
|
+
return c.json(getIntentRouterStatus());
|
|
141
|
+
});
|
|
142
|
+
app.delete('/api/intent-router/key', (c) => {
|
|
143
|
+
setIntentRouterConfig({ apiKey: null });
|
|
144
|
+
resetIntentRouter();
|
|
145
|
+
return c.json(getIntentRouterStatus());
|
|
146
|
+
});
|
|
147
|
+
app.post('/api/intent-router/settings', async (c) => {
|
|
148
|
+
const body = (await c.req.json().catch(() => ({})));
|
|
149
|
+
setIntentRouterConfig({
|
|
150
|
+
...(typeof body.enabled === 'boolean' ? { enabled: body.enabled } : {}),
|
|
151
|
+
...(typeof body.instant === 'boolean' ? { instant: body.instant } : {}),
|
|
152
|
+
});
|
|
153
|
+
resetIntentRouter();
|
|
154
|
+
return c.json(getIntentRouterStatus());
|
|
155
|
+
});
|
|
156
|
+
// Jev intent preview while the user types. Off unless a TypeSafe key is set.
|
|
157
|
+
app.post('/api/intent', async (c) => {
|
|
158
|
+
if (!isIntentRouterEnabled())
|
|
159
|
+
return c.json({ enabled: false });
|
|
160
|
+
const body = (await c.req.json().catch(() => ({})));
|
|
161
|
+
const prompt = typeof body.prompt === 'string' ? body.prompt.trim() : '';
|
|
162
|
+
if (prompt.length < 3)
|
|
163
|
+
return c.json({ enabled: true, decision: null });
|
|
164
|
+
try {
|
|
165
|
+
const routed = await routeIntent(prompt, c.req.raw.signal);
|
|
166
|
+
return c.json({
|
|
167
|
+
enabled: true,
|
|
168
|
+
decision: routed ? toRouteView(routed.decision) : null,
|
|
169
|
+
cached: routed?.cached ?? false,
|
|
170
|
+
});
|
|
171
|
+
}
|
|
172
|
+
catch (err) {
|
|
173
|
+
return c.json({ enabled: true, decision: null, error: err.message });
|
|
174
|
+
}
|
|
175
|
+
});
|
|
123
176
|
app.post('/api/config/action-plan', async (c) => {
|
|
124
177
|
const body = await c.req.json().catch(() => ({}));
|
|
125
178
|
const next = saveConfig({ actionPlanBeta: Boolean(body.enabled) });
|
|
@@ -365,9 +418,24 @@ export async function startUIServer(opts) {
|
|
|
365
418
|
return c.json({ ok: true });
|
|
366
419
|
});
|
|
367
420
|
app.delete('/api/chats/:id', (c) => {
|
|
368
|
-
|
|
421
|
+
const id = c.req.param('id');
|
|
422
|
+
deleteChat(id);
|
|
423
|
+
deletePreviewImages(id);
|
|
369
424
|
return c.json({ ok: true });
|
|
370
425
|
});
|
|
426
|
+
// Tool images (photoshop_get_preview) are stored per chat on disk; the browser
|
|
427
|
+
// fetches them with the session token and shows them as object URLs.
|
|
428
|
+
app.get('/api/chats/:id/previews/:file', (c) => {
|
|
429
|
+
const image = readPreviewImage(c.req.param('id'), c.req.param('file'));
|
|
430
|
+
if (!image)
|
|
431
|
+
return c.json({ error: 'not_found' }, 404);
|
|
432
|
+
return new Response(new Uint8Array(image.bytes), {
|
|
433
|
+
headers: {
|
|
434
|
+
'content-type': image.mimeType,
|
|
435
|
+
'cache-control': 'private, max-age=31536000, immutable',
|
|
436
|
+
},
|
|
437
|
+
});
|
|
438
|
+
});
|
|
371
439
|
// ---- Chat streaming -----------------------------------------------------
|
|
372
440
|
app.post('/api/chat', async (c) => {
|
|
373
441
|
const body = await c.req.json().catch(() => ({}));
|
|
@@ -418,6 +486,7 @@ export async function startUIServer(opts) {
|
|
|
418
486
|
let buffer = { text: '', toolCalls: [] };
|
|
419
487
|
let lastFinish = null;
|
|
420
488
|
let assistantPersisted = false;
|
|
489
|
+
let route;
|
|
421
490
|
const persistAssistant = () => {
|
|
422
491
|
if (assistantPersisted)
|
|
423
492
|
return;
|
|
@@ -437,6 +506,7 @@ export async function startUIServer(opts) {
|
|
|
437
506
|
model: chat.model,
|
|
438
507
|
...(buffer.reasoning ? { reasoning: buffer.reasoning } : {}),
|
|
439
508
|
...(buffer.plan ? { plan: buffer.plan } : {}),
|
|
509
|
+
...(route ? { route } : {}),
|
|
440
510
|
...(lastFinish?.usage ? { usage: lastFinish.usage } : {}),
|
|
441
511
|
...(lastFinish?.cost ? { cost: lastFinish.cost } : {}),
|
|
442
512
|
},
|
|
@@ -478,6 +548,8 @@ export async function startUIServer(opts) {
|
|
|
478
548
|
for await (const ev of iterator) {
|
|
479
549
|
if (controller.signal.aborted)
|
|
480
550
|
break;
|
|
551
|
+
if (ev.type === 'route')
|
|
552
|
+
route = ev.payload;
|
|
481
553
|
await stream.writeSSE({ event: ev.type, data: JSON.stringify(ev.payload) });
|
|
482
554
|
}
|
|
483
555
|
persistAssistant();
|