dot-studio 0.0.1 → 0.0.2
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 +20 -200
- package/client/assets/ActFrame-BYOBkLYW.js +1 -0
- package/client/assets/ActFrame-C_WEt6bv.css +1 -0
- package/client/assets/ActInspectorPanel-C3VlS7tB.js +1 -0
- package/client/assets/ActInspectorPanel-CE6s6GYv.css +1 -0
- package/client/assets/AssistantChat-BOyW0K79.js +1 -0
- package/client/assets/AssistantChat-DoVmHvMJ.css +1 -0
- package/client/assets/CanvasTerminalFrame-BC-79q9U.css +1 -0
- package/client/assets/CanvasTerminalFrame-DxKbexK6.js +4 -0
- package/client/assets/CanvasTrackingFrame-DumxhNwg.js +1 -0
- package/client/assets/CanvasTrackingFrame-G4rRrfne.css +1 -0
- package/client/assets/CanvasWindowFrame-ziJeVfHG.js +1 -0
- package/client/assets/DanceBundleEditorFrame-CH8VDUMK.js +1 -0
- package/client/assets/DanceBundleEditorFrame-DaLqMflT.css +1 -0
- package/client/assets/MarkdownEditorFrame-DVecIZpZ.css +1 -0
- package/client/assets/MarkdownEditorFrame-Dwpgs2GX.js +2 -0
- package/client/assets/MarkdownRenderer-Cz8A4AgP.js +1 -0
- package/client/assets/PublishModal-DUlHz0fT.js +1 -0
- package/client/assets/TodoDock-DcVf7zQG.js +1 -0
- package/client/assets/WorkspaceToolbar-CXYi_sMD.js +2 -0
- package/client/assets/WorkspaceToolbar-CiQvVocC.css +1 -0
- package/client/assets/chat-message-visibility-YwJ-AQno.js +11 -0
- package/client/assets/dnd-vendor-CIAZE2P2.js +5 -0
- package/client/assets/flow-vendor-BZV40eAE.css +1 -0
- package/client/assets/flow-vendor-C868rU-6.js +23 -0
- package/client/assets/icon-vendor-I2JVIi1s.js +501 -0
- package/client/assets/index-BMY4hrBP.js +3 -0
- package/client/assets/index-C-vnj9y3.js +1 -0
- package/client/assets/index-C9HTqfZw.css +1 -0
- package/client/assets/index-CWrv6O3o.js +64 -0
- package/client/assets/index-DMS12-Q2.js +8 -0
- package/client/assets/index-Dn7t_Y7G.js +1 -0
- package/client/assets/index-p-wk7iGH.css +1 -0
- package/client/assets/markdown-vendor-BSTcku12.css +10 -0
- package/client/assets/markdown-vendor-DnTJ9hmR.js +35 -0
- package/client/assets/participant-labels-Cf3qP3GB.js +1 -0
- package/client/assets/queries-Dm1jEHfc.js +1 -0
- package/client/assets/query-vendor-_taqgrbn.js +1 -0
- package/client/assets/react-vendor-DzpMUNDT.js +49 -0
- package/client/assets/settings-utils-l7KCS3Ev.js +1 -0
- package/client/assets/terminal-vendor-6GBZ9nXN.css +32 -0
- package/client/assets/terminal-vendor-D0xRnmbI.js +112 -0
- package/client/index.html +13 -3
- package/dist/cli.js +25 -3
- package/dist/server/app.js +72 -0
- package/dist/server/index.js +2 -62
- package/dist/server/lib/act-session-policy.js +31 -0
- package/dist/server/lib/chat-session.js +101 -0
- package/dist/server/lib/config.js +18 -4
- package/dist/server/lib/dot-authoring.js +171 -102
- package/dist/server/lib/dot-loader.js +9 -8
- package/dist/server/lib/dot-login.js +8 -190
- package/dist/server/lib/dot-source.js +11 -0
- package/dist/server/lib/model-catalog.js +74 -15
- package/dist/server/lib/opencode-auth.js +4 -1
- package/dist/server/lib/opencode-errors.js +70 -38
- package/dist/server/lib/opencode-sidecar.js +5 -2
- package/dist/server/lib/project-config.js +8 -0
- package/dist/server/lib/runtime-tools.js +46 -8
- package/dist/server/lib/safe-mode.js +410 -0
- package/dist/server/lib/session-execution.js +81 -0
- package/dist/server/lib/sse.js +22 -0
- package/dist/server/routes/act-runtime-threads.js +156 -0
- package/dist/server/routes/act-runtime-tools.js +157 -0
- package/dist/server/routes/act-runtime.js +7 -0
- package/dist/server/routes/adapter.js +32 -0
- package/dist/server/routes/assets-collection.js +16 -0
- package/dist/server/routes/assets-detail.js +38 -0
- package/dist/server/routes/assets.js +4 -158
- package/dist/server/routes/chat-messages.js +104 -0
- package/dist/server/routes/chat-sessions.js +104 -0
- package/dist/server/routes/chat-stream.js +15 -0
- package/dist/server/routes/chat.js +6 -353
- package/dist/server/routes/compile.js +5 -91
- package/dist/server/routes/dot-assets.js +77 -0
- package/dist/server/routes/dot-core.js +62 -0
- package/dist/server/routes/dot-performer.js +80 -0
- package/dist/server/routes/dot.js +6 -267
- package/dist/server/routes/drafts-collection.js +40 -0
- package/dist/server/routes/drafts-dance-bundle.js +113 -0
- package/dist/server/routes/drafts-item.js +86 -0
- package/dist/server/routes/drafts.js +9 -0
- package/dist/server/routes/health.js +18 -33
- package/dist/server/routes/opencode-core.js +120 -0
- package/dist/server/routes/opencode-file.js +67 -0
- package/dist/server/routes/opencode-mcp.js +74 -0
- package/dist/server/routes/opencode-provider.js +41 -0
- package/dist/server/routes/opencode.js +8 -418
- package/dist/server/routes/route-errors.js +10 -0
- package/dist/server/routes/safe-actions.js +60 -0
- package/dist/server/routes/safe-summary.js +20 -0
- package/dist/server/routes/safe.js +7 -0
- package/dist/server/routes/workspaces.js +47 -0
- package/dist/server/services/act-runtime/act-context-builder.js +81 -0
- package/dist/server/services/act-runtime/act-runtime-service.js +313 -0
- package/dist/server/services/act-runtime/act-runtime-utils.js +10 -0
- package/dist/server/services/act-runtime/act-tool-projection.js +26 -0
- package/dist/server/services/act-runtime/act-tools.js +151 -0
- package/dist/server/services/act-runtime/board-persistence.js +38 -0
- package/dist/server/services/act-runtime/event-logger.js +73 -0
- package/dist/server/services/act-runtime/event-router.js +102 -0
- package/dist/server/services/act-runtime/mailbox.js +149 -0
- package/dist/server/services/act-runtime/safety-guard.js +162 -0
- package/dist/server/services/act-runtime/session-queue.js +114 -0
- package/dist/server/services/act-runtime/thread-manager.js +351 -0
- package/dist/server/services/act-runtime/wake-cascade.js +306 -0
- package/dist/server/services/act-runtime/wake-evaluator.js +43 -0
- package/dist/server/services/act-runtime/wake-performer-resolver.js +68 -0
- package/dist/server/services/act-runtime/wake-prompt-builder.js +77 -0
- package/dist/server/services/adapter-view-service.js +6 -0
- package/dist/server/services/asset-service.js +366 -0
- package/dist/server/services/chat-event-stream-service.js +157 -0
- package/dist/server/services/chat-service.js +207 -0
- package/dist/server/services/chat-session-service.js +203 -0
- package/dist/server/services/compile-service.js +4 -0
- package/dist/server/services/dance-bundle-service.js +222 -0
- package/dist/server/services/dot-add-service.js +59 -0
- package/dist/server/services/dot-service.js +178 -0
- package/dist/server/services/draft-service.js +367 -0
- package/dist/server/services/opencode-projection/dance-compiler.js +164 -0
- package/dist/server/services/opencode-projection/performer-compiler.js +195 -0
- package/dist/server/services/opencode-projection/preview-service.js +31 -0
- package/dist/server/services/opencode-projection/projection-manifest.js +98 -0
- package/dist/server/services/opencode-projection/stage-projection-service.js +188 -0
- package/dist/server/services/opencode-service.js +338 -0
- package/dist/server/services/safe-service.js +33 -0
- package/dist/server/services/studio-assistant/assistant-service.js +172 -0
- package/dist/server/services/studio-service.js +69 -0
- package/dist/server/services/workspace-service.js +224 -0
- package/dist/server/terminal.js +57 -11
- package/dist/shared/act-types.js +4 -0
- package/dist/shared/adapter-view.js +1 -0
- package/dist/shared/asset-contracts.js +1 -0
- package/dist/shared/assistant-actions.js +1 -0
- package/dist/shared/chat-contracts.js +1 -0
- package/dist/shared/dot-contracts.js +1 -0
- package/dist/shared/dot-types.js +4 -0
- package/dist/shared/draft-contracts.js +2 -0
- package/dist/shared/model-types.js +2 -0
- package/dist/shared/performer-mcp-portability.js +10 -0
- package/dist/shared/safe-mode.js +1 -0
- package/dist/shared/session-metadata.js +4 -3
- package/package.json +6 -4
- package/client/assets/index-C2eIILoa.css +0 -41
- package/client/assets/index-DUPZ_Lw5.js +0 -616
- package/dist/server/lib/act-runtime.js +0 -1282
- package/dist/server/lib/prompt.js +0 -222
- package/dist/server/routes/stages.js +0 -137
|
@@ -1,6 +1,15 @@
|
|
|
1
1
|
import { getOpencode } from './opencode.js';
|
|
2
2
|
import { readStoredProviderAuthType } from './opencode-auth.js';
|
|
3
3
|
import { normalizeRuntimeVariants, } from '../../shared/model-variants.js';
|
|
4
|
+
function responseData(response) {
|
|
5
|
+
if (!response || typeof response !== 'object' || !('data' in response)) {
|
|
6
|
+
return undefined;
|
|
7
|
+
}
|
|
8
|
+
return response.data;
|
|
9
|
+
}
|
|
10
|
+
function asRecord(value) {
|
|
11
|
+
return value && typeof value === 'object' ? value : {};
|
|
12
|
+
}
|
|
4
13
|
const incompatibleModelsByAuthType = {
|
|
5
14
|
openai: {
|
|
6
15
|
// ChatGPT account-backed OpenAI auth rejects these at runtime today.
|
|
@@ -11,9 +20,7 @@ const incompatibleModelsByAuthType = {
|
|
|
11
20
|
},
|
|
12
21
|
};
|
|
13
22
|
function readCapabilityFlag(model, ...keys) {
|
|
14
|
-
const capabilityRecord = model.capabilities
|
|
15
|
-
? model.capabilities
|
|
16
|
-
: {};
|
|
23
|
+
const capabilityRecord = asRecord(model.capabilities);
|
|
17
24
|
for (const key of keys) {
|
|
18
25
|
if (typeof capabilityRecord[key] === 'boolean') {
|
|
19
26
|
return capabilityRecord[key];
|
|
@@ -25,18 +32,17 @@ function readCapabilityFlag(model, ...keys) {
|
|
|
25
32
|
return false;
|
|
26
33
|
}
|
|
27
34
|
function readModalities(model) {
|
|
28
|
-
const capabilityRecord = model.capabilities
|
|
29
|
-
|
|
30
|
-
: {};
|
|
35
|
+
const capabilityRecord = asRecord(model.capabilities);
|
|
36
|
+
const modalityRecord = asRecord(model.modalities);
|
|
31
37
|
const input = Array.isArray(capabilityRecord.input)
|
|
32
38
|
? capabilityRecord.input.filter((value) => typeof value === 'string')
|
|
33
|
-
: Array.isArray(
|
|
34
|
-
?
|
|
39
|
+
: Array.isArray(modalityRecord.input)
|
|
40
|
+
? modalityRecord.input.filter((value) => typeof value === 'string')
|
|
35
41
|
: ['text'];
|
|
36
42
|
const output = Array.isArray(capabilityRecord.output)
|
|
37
43
|
? capabilityRecord.output.filter((value) => typeof value === 'string')
|
|
38
|
-
: Array.isArray(
|
|
39
|
-
?
|
|
44
|
+
: Array.isArray(modalityRecord.output)
|
|
45
|
+
? modalityRecord.output.filter((value) => typeof value === 'string')
|
|
40
46
|
: ['text'];
|
|
41
47
|
return { input, output };
|
|
42
48
|
}
|
|
@@ -50,10 +56,62 @@ function isModelVisibleForAuthType(providerId, modelId, authType) {
|
|
|
50
56
|
}
|
|
51
57
|
return !blocked.has(modelId);
|
|
52
58
|
}
|
|
59
|
+
// ── Cached provider.list() ──────────────────────────────
|
|
60
|
+
// Both /api/providers and /api/models need the same raw data from
|
|
61
|
+
// oc.provider.list(). We cache for a short window to avoid duplicate
|
|
62
|
+
// round trips when the two routes are hit close together (which is the
|
|
63
|
+
// common case — the client fetches both on Settings open / refresh).
|
|
64
|
+
const CACHE_TTL_MS = 3_000;
|
|
65
|
+
let _cachedPromise = null;
|
|
66
|
+
let _cachedCwd = null;
|
|
67
|
+
let _cacheTs = 0;
|
|
68
|
+
/**
|
|
69
|
+
* Fetch the raw oc.provider.list() data with a short TTL cache
|
|
70
|
+
* keyed on the working directory.
|
|
71
|
+
*/
|
|
72
|
+
export async function fetchProviderListData(cwd) {
|
|
73
|
+
const now = Date.now();
|
|
74
|
+
if (_cachedPromise && _cachedCwd === cwd && now - _cacheTs < CACHE_TTL_MS) {
|
|
75
|
+
return _cachedPromise;
|
|
76
|
+
}
|
|
77
|
+
_cachedCwd = cwd;
|
|
78
|
+
_cacheTs = now;
|
|
79
|
+
_cachedPromise = (async () => {
|
|
80
|
+
const oc = await getOpencode();
|
|
81
|
+
const res = await oc.provider.list({ directory: cwd });
|
|
82
|
+
return responseData(res);
|
|
83
|
+
})();
|
|
84
|
+
// On failure, clear the cache so the next call retries immediately.
|
|
85
|
+
_cachedPromise.catch(() => {
|
|
86
|
+
_cachedPromise = null;
|
|
87
|
+
});
|
|
88
|
+
return _cachedPromise;
|
|
89
|
+
}
|
|
90
|
+
/** Invalidate the cache (e.g. after auth changes). */
|
|
91
|
+
export function invalidateProviderListCache() {
|
|
92
|
+
_cachedPromise = null;
|
|
93
|
+
_cachedCwd = null;
|
|
94
|
+
_cacheTs = 0;
|
|
95
|
+
}
|
|
96
|
+
export async function listProviderSummaries(cwd) {
|
|
97
|
+
const data = await fetchProviderListData(cwd);
|
|
98
|
+
if (!data?.all || !Array.isArray(data.all)) {
|
|
99
|
+
return [];
|
|
100
|
+
}
|
|
101
|
+
const connected = new Set((data?.connected || []));
|
|
102
|
+
return data.all.map((provider) => ({
|
|
103
|
+
id: typeof provider.id === 'string' ? provider.id : '',
|
|
104
|
+
name: typeof provider.name === 'string' ? provider.name : (typeof provider.id === 'string' ? provider.id : ''),
|
|
105
|
+
source: typeof provider.source === 'string' ? provider.source : 'builtin',
|
|
106
|
+
env: Array.isArray(provider.env) ? provider.env.filter((value) => typeof value === 'string') : [],
|
|
107
|
+
connected: typeof provider.id === 'string' ? connected.has(provider.id) : false,
|
|
108
|
+
modelCount: provider.models ? Object.keys(provider.models).length : 0,
|
|
109
|
+
defaultModel: typeof provider.id === 'string' ? data?.default?.[provider.id] || null : null,
|
|
110
|
+
}));
|
|
111
|
+
}
|
|
112
|
+
// ── Model catalog (used by /api/models) ─────────────────
|
|
53
113
|
export async function listRuntimeModels(cwd) {
|
|
54
|
-
const
|
|
55
|
-
const res = await oc.provider.list({ directory: cwd });
|
|
56
|
-
const data = res.data;
|
|
114
|
+
const data = await fetchProviderListData(cwd);
|
|
57
115
|
if (!data?.all || !Array.isArray(data.all)) {
|
|
58
116
|
return [];
|
|
59
117
|
}
|
|
@@ -82,14 +140,15 @@ export async function listRuntimeModels(cwd) {
|
|
|
82
140
|
if (!isModelVisibleForAuthType(providerId, id, authType)) {
|
|
83
141
|
continue;
|
|
84
142
|
}
|
|
143
|
+
const limitRecord = asRecord(record.limit);
|
|
85
144
|
models.push({
|
|
86
145
|
provider: providerId,
|
|
87
146
|
providerName,
|
|
88
147
|
id,
|
|
89
148
|
name: typeof record.name === 'string' ? record.name : id,
|
|
90
149
|
connected,
|
|
91
|
-
context: Number(
|
|
92
|
-
output: Number(
|
|
150
|
+
context: Number(limitRecord.context || 0),
|
|
151
|
+
output: Number(limitRecord.output || 0),
|
|
93
152
|
toolCall: readCapabilityFlag(record, 'toolcall', 'toolCall', 'tool_call'),
|
|
94
153
|
reasoning: readCapabilityFlag(record, 'reasoning'),
|
|
95
154
|
attachment: readCapabilityFlag(record, 'attachment'),
|
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
import fs from 'fs/promises';
|
|
2
2
|
import os from 'os';
|
|
3
3
|
import path from 'path';
|
|
4
|
+
function isErrnoException(error) {
|
|
5
|
+
return error instanceof Error;
|
|
6
|
+
}
|
|
4
7
|
function authStoreCandidates() {
|
|
5
8
|
const home = os.homedir();
|
|
6
9
|
const candidates = [
|
|
@@ -55,7 +58,7 @@ export async function clearStoredProviderAuth(providerId) {
|
|
|
55
58
|
current = JSON.parse(raw);
|
|
56
59
|
}
|
|
57
60
|
catch (error) {
|
|
58
|
-
if (error
|
|
61
|
+
if (!isErrnoException(error) || error.code !== 'ENOENT') {
|
|
59
62
|
throw error;
|
|
60
63
|
}
|
|
61
64
|
}
|
|
@@ -1,3 +1,25 @@
|
|
|
1
|
+
function asRecord(value) {
|
|
2
|
+
return value && typeof value === 'object' ? value : undefined;
|
|
3
|
+
}
|
|
4
|
+
function readPath(value, ...keys) {
|
|
5
|
+
let current = value;
|
|
6
|
+
for (const key of keys) {
|
|
7
|
+
const record = asRecord(current);
|
|
8
|
+
if (!record) {
|
|
9
|
+
return undefined;
|
|
10
|
+
}
|
|
11
|
+
current = record[key];
|
|
12
|
+
}
|
|
13
|
+
return current;
|
|
14
|
+
}
|
|
15
|
+
function readString(value, ...keys) {
|
|
16
|
+
const candidate = readPath(value, ...keys);
|
|
17
|
+
return typeof candidate === 'string' ? candidate : undefined;
|
|
18
|
+
}
|
|
19
|
+
function readNumber(value, ...keys) {
|
|
20
|
+
const candidate = readPath(value, ...keys);
|
|
21
|
+
return typeof candidate === 'number' && Number.isFinite(candidate) ? candidate : undefined;
|
|
22
|
+
}
|
|
1
23
|
export class StudioValidationError extends Error {
|
|
2
24
|
action;
|
|
3
25
|
status;
|
|
@@ -10,16 +32,16 @@ export class StudioValidationError extends Error {
|
|
|
10
32
|
}
|
|
11
33
|
function extractStatus(err) {
|
|
12
34
|
const candidates = [
|
|
13
|
-
err
|
|
14
|
-
err
|
|
15
|
-
err
|
|
16
|
-
err
|
|
17
|
-
err
|
|
18
|
-
err
|
|
19
|
-
err
|
|
35
|
+
readNumber(err, 'status'),
|
|
36
|
+
readNumber(err, 'statusCode'),
|
|
37
|
+
readNumber(err, 'data', 'statusCode'),
|
|
38
|
+
readNumber(err, 'response', 'status'),
|
|
39
|
+
readNumber(err, 'cause', 'status'),
|
|
40
|
+
readNumber(err, 'cause', 'statusCode'),
|
|
41
|
+
readNumber(err, 'cause', 'response', 'status'),
|
|
20
42
|
];
|
|
21
43
|
for (const candidate of candidates) {
|
|
22
|
-
if (
|
|
44
|
+
if (candidate !== undefined) {
|
|
23
45
|
return candidate;
|
|
24
46
|
}
|
|
25
47
|
}
|
|
@@ -31,13 +53,13 @@ function extractBodyMessage(body) {
|
|
|
31
53
|
}
|
|
32
54
|
try {
|
|
33
55
|
const parsed = JSON.parse(body);
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
56
|
+
const error = readString(parsed, 'error');
|
|
57
|
+
if (error?.trim()) {
|
|
58
|
+
return error.trim();
|
|
59
|
+
}
|
|
60
|
+
const message = readString(parsed, 'message');
|
|
61
|
+
if (message?.trim()) {
|
|
62
|
+
return message.trim();
|
|
41
63
|
}
|
|
42
64
|
}
|
|
43
65
|
catch {
|
|
@@ -47,20 +69,31 @@ function extractBodyMessage(body) {
|
|
|
47
69
|
}
|
|
48
70
|
function extractMessage(err) {
|
|
49
71
|
const message = [
|
|
50
|
-
err
|
|
51
|
-
err
|
|
52
|
-
err
|
|
53
|
-
err
|
|
54
|
-
err
|
|
55
|
-
extractBodyMessage(err
|
|
56
|
-
extractBodyMessage(err
|
|
72
|
+
readString(err, 'data', 'message'),
|
|
73
|
+
readString(err, 'message'),
|
|
74
|
+
readString(err, 'error', 'message'),
|
|
75
|
+
readString(err, 'cause', 'data', 'message'),
|
|
76
|
+
readString(err, 'cause', 'message'),
|
|
77
|
+
extractBodyMessage(readPath(err, 'data', 'responseBody')),
|
|
78
|
+
extractBodyMessage(readPath(err, 'responseBody')),
|
|
57
79
|
].find((candidate) => typeof candidate === 'string' && candidate.trim().length > 0);
|
|
58
|
-
|
|
80
|
+
if (message) {
|
|
81
|
+
return message;
|
|
82
|
+
}
|
|
83
|
+
// Stringify raw error for debuggability — truncate if huge
|
|
84
|
+
try {
|
|
85
|
+
const raw = JSON.stringify(err, null, 2);
|
|
86
|
+
const truncated = raw.length > 500 ? raw.slice(0, 500) + '…' : raw;
|
|
87
|
+
return `OpenCode request failed. Raw: ${truncated}`;
|
|
88
|
+
}
|
|
89
|
+
catch {
|
|
90
|
+
return 'OpenCode request failed.';
|
|
91
|
+
}
|
|
59
92
|
}
|
|
60
93
|
function extractProviderId(err, context) {
|
|
61
94
|
return context.providerId?.trim()
|
|
62
|
-
|| err
|
|
63
|
-
|| err
|
|
95
|
+
|| readString(err, 'data', 'providerID')
|
|
96
|
+
|| readString(err, 'providerId')
|
|
64
97
|
|| context.model?.provider
|
|
65
98
|
|| undefined;
|
|
66
99
|
}
|
|
@@ -103,17 +136,16 @@ export function normalizeOpencodeError(err, context = {}) {
|
|
|
103
136
|
status: err.status,
|
|
104
137
|
};
|
|
105
138
|
}
|
|
106
|
-
const
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
? raw.error.name
|
|
139
|
+
const name = typeof readString(err, 'name') === 'string'
|
|
140
|
+
? readString(err, 'name')
|
|
141
|
+
: typeof readString(err, 'error', 'name') === 'string'
|
|
142
|
+
? readString(err, 'error', 'name')
|
|
111
143
|
: 'UnknownError';
|
|
112
|
-
const detail = extractMessage(
|
|
113
|
-
const status = extractStatus(
|
|
114
|
-
const providerId = extractProviderId(
|
|
144
|
+
const detail = extractMessage(err);
|
|
145
|
+
const status = extractStatus(err);
|
|
146
|
+
const providerId = extractProviderId(err, context);
|
|
115
147
|
const modelId = context.model?.modelId;
|
|
116
|
-
const retryable =
|
|
148
|
+
const retryable = readPath(err, 'data', 'isRetryable') === true || (!!status && status >= 500);
|
|
117
149
|
if (isProviderAuthError(name, detail, status)) {
|
|
118
150
|
return {
|
|
119
151
|
error: `Provider authentication is missing or expired${providerId ? ` for ${providerId}` : ''}. Reconnect it in Settings and try again.`,
|
|
@@ -202,14 +234,14 @@ export function jsonOpencodeError(c, err, context = {}) {
|
|
|
202
234
|
return c.json(payload, payload.status);
|
|
203
235
|
}
|
|
204
236
|
export function unwrapOpencodeResult(result) {
|
|
205
|
-
const value = result;
|
|
206
|
-
if (value &&
|
|
237
|
+
const value = asRecord(result);
|
|
238
|
+
if (value && 'error' in value && value.error) {
|
|
207
239
|
throw value.error;
|
|
208
240
|
}
|
|
209
|
-
if (value &&
|
|
241
|
+
if (value && 'data' in value) {
|
|
210
242
|
return value.data;
|
|
211
243
|
}
|
|
212
|
-
return
|
|
244
|
+
return result;
|
|
213
245
|
}
|
|
214
246
|
export function unwrapPromptResult(result) {
|
|
215
247
|
const data = unwrapOpencodeResult(result);
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { spawn } from 'child_process';
|
|
2
2
|
import path from 'path';
|
|
3
|
-
import { DEFAULT_PROJECT_DIR, OPENCODE_MANAGED, OPENCODE_URL } from './config.js';
|
|
3
|
+
import { DEFAULT_PROJECT_DIR, OPENCODE_MANAGED, OPENCODE_URL, STUDIO_DIR } from './config.js';
|
|
4
4
|
import { resolvePackageBin } from './package-bin.js';
|
|
5
5
|
const STARTUP_TIMEOUT_MS = 15_000;
|
|
6
6
|
const HEALTHCHECK_INTERVAL_MS = 250;
|
|
@@ -105,7 +105,10 @@ export async function ensureOpencodeSidecar() {
|
|
|
105
105
|
}
|
|
106
106
|
const opencode = spawn(resolveCommand(), ['--port', String(resolvePort()), path.resolve(DEFAULT_PROJECT_DIR)], {
|
|
107
107
|
cwd: path.resolve(DEFAULT_PROJECT_DIR),
|
|
108
|
-
env:
|
|
108
|
+
env: {
|
|
109
|
+
...process.env,
|
|
110
|
+
OPENCODE_CONFIG_DIR: path.join(STUDIO_DIR, 'opencode'),
|
|
111
|
+
},
|
|
109
112
|
stdio: 'ignore',
|
|
110
113
|
});
|
|
111
114
|
child = opencode;
|
|
@@ -25,6 +25,9 @@ export function summarizeProjectMcpCatalog(catalog, liveStatus) {
|
|
|
25
25
|
const config = catalog[name];
|
|
26
26
|
const live = liveStatus[name];
|
|
27
27
|
const status = live?.status || (config ? (projectMcpEntryEnabled(config) ? 'disconnected' : 'disabled') : 'unknown');
|
|
28
|
+
const oauthConfig = config && 'type' in config && config.type === 'remote'
|
|
29
|
+
? config.oauth
|
|
30
|
+
: undefined;
|
|
28
31
|
return {
|
|
29
32
|
name,
|
|
30
33
|
status,
|
|
@@ -34,6 +37,11 @@ export function summarizeProjectMcpCatalog(catalog, liveStatus) {
|
|
|
34
37
|
defined: !!config,
|
|
35
38
|
configType: projectMcpEntryType(config),
|
|
36
39
|
authStatus: status === 'needs_auth' ? 'needs_auth' : status === 'connected' ? 'ready' : 'n/a',
|
|
40
|
+
error: typeof live?.error === 'string' ? live.error : undefined,
|
|
41
|
+
oauthConfigured: !!(oauthConfig
|
|
42
|
+
&& typeof oauthConfig === 'object'
|
|
43
|
+
&& (oauthConfig.clientId || oauthConfig.clientSecret || oauthConfig.scope)),
|
|
44
|
+
clientRegistrationRequired: status === 'needs_client_registration',
|
|
37
45
|
};
|
|
38
46
|
});
|
|
39
47
|
}
|
|
@@ -4,6 +4,23 @@ import { readProjectMcpCatalog } from './project-config.js';
|
|
|
4
4
|
function unique(values) {
|
|
5
5
|
return Array.from(new Set(values.filter(Boolean)));
|
|
6
6
|
}
|
|
7
|
+
function responseData(response, fallback) {
|
|
8
|
+
if (!response || typeof response !== 'object' || !('data' in response)) {
|
|
9
|
+
return fallback;
|
|
10
|
+
}
|
|
11
|
+
return (response.data ?? fallback);
|
|
12
|
+
}
|
|
13
|
+
function errorMessage(error, fallback) {
|
|
14
|
+
return error instanceof Error && error.message ? error.message : fallback;
|
|
15
|
+
}
|
|
16
|
+
function toolNames(entry) {
|
|
17
|
+
return (entry?.tools || []).map((tool) => {
|
|
18
|
+
if (!tool || typeof tool !== 'object' || !('name' in tool) || typeof tool.name !== 'string') {
|
|
19
|
+
return '';
|
|
20
|
+
}
|
|
21
|
+
return tool.name;
|
|
22
|
+
}).filter(Boolean);
|
|
23
|
+
}
|
|
7
24
|
export function describeUnavailableRuntimeTools(resolution) {
|
|
8
25
|
if (resolution.selectedMcpServers.length === 0 || resolution.unavailableDetails.length === 0) {
|
|
9
26
|
return null;
|
|
@@ -15,6 +32,9 @@ export function describeUnavailableRuntimeTools(resolution) {
|
|
|
15
32
|
if (detail.reason === 'needs_auth') {
|
|
16
33
|
return `${detail.serverName}: authentication required`;
|
|
17
34
|
}
|
|
35
|
+
if (detail.reason === 'needs_client_registration') {
|
|
36
|
+
return `${detail.serverName}: OAuth client registration required`;
|
|
37
|
+
}
|
|
18
38
|
if (detail.reason === 'disabled') {
|
|
19
39
|
return `${detail.serverName}: disabled in project config`;
|
|
20
40
|
}
|
|
@@ -47,7 +67,7 @@ function emptyResolution(selectedMcpServers) {
|
|
|
47
67
|
}
|
|
48
68
|
async function currentMcpStatus(oc, cwd) {
|
|
49
69
|
const res = await oc.mcp.status({ directory: cwd });
|
|
50
|
-
return (res
|
|
70
|
+
return responseData(res, {});
|
|
51
71
|
}
|
|
52
72
|
async function ensureConnectedServer(oc, cwd, serverName, catalog, statusMap) {
|
|
53
73
|
const config = catalog[serverName];
|
|
@@ -88,6 +108,16 @@ async function ensureConnectedServer(oc, cwd, serverName, catalog, statusMap) {
|
|
|
88
108
|
},
|
|
89
109
|
};
|
|
90
110
|
}
|
|
111
|
+
if (current?.status === 'needs_client_registration') {
|
|
112
|
+
return {
|
|
113
|
+
statusMap,
|
|
114
|
+
unavailable: {
|
|
115
|
+
serverName,
|
|
116
|
+
reason: 'needs_client_registration',
|
|
117
|
+
detail: current?.error || 'Server requires OAuth client registration before it can connect.',
|
|
118
|
+
},
|
|
119
|
+
};
|
|
120
|
+
}
|
|
91
121
|
try {
|
|
92
122
|
await oc.mcp.connect({
|
|
93
123
|
name: serverName,
|
|
@@ -100,7 +130,7 @@ async function ensureConnectedServer(oc, cwd, serverName, catalog, statusMap) {
|
|
|
100
130
|
unavailable: {
|
|
101
131
|
serverName,
|
|
102
132
|
reason: 'connect_failed',
|
|
103
|
-
detail: error
|
|
133
|
+
detail: errorMessage(error, 'Connection attempt failed.'),
|
|
104
134
|
},
|
|
105
135
|
};
|
|
106
136
|
}
|
|
@@ -122,6 +152,16 @@ async function ensureConnectedServer(oc, cwd, serverName, catalog, statusMap) {
|
|
|
122
152
|
},
|
|
123
153
|
};
|
|
124
154
|
}
|
|
155
|
+
if (next?.status === 'needs_client_registration') {
|
|
156
|
+
return {
|
|
157
|
+
statusMap: refreshed,
|
|
158
|
+
unavailable: {
|
|
159
|
+
serverName,
|
|
160
|
+
reason: 'needs_client_registration',
|
|
161
|
+
detail: next?.error || 'Server requires OAuth client registration before it can connect.',
|
|
162
|
+
},
|
|
163
|
+
};
|
|
164
|
+
}
|
|
125
165
|
return {
|
|
126
166
|
statusMap: refreshed,
|
|
127
167
|
unavailable: {
|
|
@@ -147,8 +187,7 @@ export async function resolveRuntimeTools(cwd, model, mcpServerNames) {
|
|
|
147
187
|
unavailableDetails.push(ensured.unavailable);
|
|
148
188
|
}
|
|
149
189
|
}
|
|
150
|
-
const requestedTools = unique(selectedMcpServers.flatMap((serverName) => (mcpStatus[serverName]
|
|
151
|
-
.map((tool) => tool.name || '')));
|
|
190
|
+
const requestedTools = unique(selectedMcpServers.flatMap((serverName) => toolNames(mcpStatus[serverName])));
|
|
152
191
|
if (requestedTools.length === 0) {
|
|
153
192
|
return {
|
|
154
193
|
...emptyResolution(selectedMcpServers),
|
|
@@ -162,22 +201,21 @@ export async function resolveRuntimeTools(cwd, model, mcpServerNames) {
|
|
|
162
201
|
model: model.modelId,
|
|
163
202
|
directory: cwd,
|
|
164
203
|
});
|
|
165
|
-
const items = (toolListRes
|
|
204
|
+
const items = responseData(toolListRes, []);
|
|
166
205
|
availableTools = unique(items.map((item) => item.id || ''));
|
|
167
206
|
}
|
|
168
207
|
else {
|
|
169
208
|
const toolIdsRes = await oc.tool.ids({
|
|
170
209
|
directory: cwd,
|
|
171
210
|
});
|
|
172
|
-
availableTools = unique((toolIdsRes
|
|
211
|
+
availableTools = unique(responseData(toolIdsRes, []));
|
|
173
212
|
}
|
|
174
213
|
const availableSet = new Set(availableTools);
|
|
175
214
|
const resolvedTools = requestedTools.filter((toolId) => availableSet.has(toolId));
|
|
176
215
|
const unavailableTools = requestedTools.filter((toolId) => !availableSet.has(toolId));
|
|
177
216
|
const toolServerNames = new Map();
|
|
178
217
|
for (const serverName of selectedMcpServers) {
|
|
179
|
-
for (const
|
|
180
|
-
const toolId = tool.name || '';
|
|
218
|
+
for (const toolId of toolNames(mcpStatus[serverName])) {
|
|
181
219
|
if (!toolId)
|
|
182
220
|
continue;
|
|
183
221
|
const current = toolServerNames.get(toolId) || [];
|