@openchambery/web 2.0.0-beta.26 → 2.0.0-beta.28
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/assets/{AgentAvatar-FyTdEOMr.js → AgentAvatar-CqD6Pfom.js} +1 -1
- package/dist/assets/{AssistantView-DvOO2Kf5.js → AssistantView-BW9f9vF2.js} +3 -3
- package/dist/assets/{DiagramView-BqjeSOIg.js → DiagramView-BP3WDmFe.js} +1 -1
- package/dist/assets/{FadeInOnReveal-DHDffzHs.js → FadeInOnReveal-ezW_e-yz.js} +1 -1
- package/dist/assets/{Icon-D7NdMmbi.js → Icon-ChOAx1jX.js} +1 -1
- package/dist/assets/{MarkdownRenderer-kpkRqS42.js → MarkdownRenderer-BBHZa1Ts.js} +2 -2
- package/dist/assets/{MarkdownRendererImpl-CzBos-Gt.js → MarkdownRendererImpl-DfxEsttZ.js} +2 -2
- package/dist/assets/{MarkstreamRendererImpl-RY_rAgKD.js → MarkstreamRendererImpl-6k-n071l.js} +1 -1
- package/dist/assets/{MobileDetailNavigation-DgtRh0zu.js → MobileDetailNavigation-s48GkQY9.js} +1 -1
- package/dist/assets/{MobileShareBridge-BM3cN-gG.js → MobileShareBridge-DAgShkFE.js} +2 -2
- package/dist/assets/MobileSurface-DZug2f4O.js +2 -0
- package/dist/assets/{MultiRunWindow-Bq4gz78C.js → MultiRunWindow-CIhHw9mo.js} +1 -1
- package/dist/assets/{OnboardingScreen-DMbPMwlp.js → OnboardingScreen-ozxwbzDw.js} +1 -1
- package/dist/assets/{OverlayScrollbar-mI9WVa7E.js → OverlayScrollbar-BL-QnnrN.js} +1 -1
- package/dist/assets/SettingsView-DmUdWSji.js +25 -0
- package/dist/assets/{SettingsWindow-CHPjbEnI.js → SettingsWindow-CuIZwGhM.js} +1 -1
- package/dist/assets/{TerminalView-DCJuL2nP.js → TerminalView-BIJ-ILgl.js} +1 -1
- package/dist/assets/{ToolOutputDialog-Cg_Q1t3X.js → ToolOutputDialog-B6KIxVUo.js} +1 -1
- package/dist/assets/{appThemeRegistry-BxuflaQt.js → appThemeRegistry-BuMGUGUK.js} +1 -1
- package/dist/assets/{button-e8fYWIMM.js → button-DoDvdeG-.js} +1 -1
- package/dist/assets/{collapsible-B6wLXHi3.js → collapsible-CZkyFVK0.js} +1 -1
- package/dist/assets/{context-menu-Bq7W1d7y.js → context-menu-DTjON-Ta.js} +1 -1
- package/dist/assets/{desktopHosts-C8vf9vRY.js → desktopHosts-CP1YHQ4Z.js} +1 -1
- package/dist/assets/{desktopRelayRestore-DDJtljeu.js → desktopRelayRestore-BeOGO7oA.js} +1 -1
- package/dist/assets/{diagram-BBL-LfsC.js → diagram-DUi801df.js} +1 -1
- package/dist/assets/{dialog-MRsJpHzu.js → dialog-DGzGqOhG.js} +1 -1
- package/dist/assets/{es-DvPwAj0y.js → es-DCjl24dV.js} +3 -3
- package/dist/assets/fileMentionTouchSelection-C3A6XKRM.js +2 -0
- package/dist/assets/{fileQueries-CBu-ytXS.js → fileQueries-CAH8-p27.js} +1 -1
- package/dist/assets/{fr-XQsoaNKY.js → fr-DfJ5j0t5.js} +3 -3
- package/dist/assets/{gitApi-CuIUGt04.js → gitApi-B1Nyw0PD.js} +3 -3
- package/dist/assets/{input-DbOe1mdT.js → input-Ct2uaiDc.js} +1 -1
- package/dist/assets/insertionBoundaries-BypTPUfp.js +1 -0
- package/dist/assets/{ja-DslzO96s.js → ja-B_9_w2Xf.js} +3 -3
- package/dist/assets/{ko-C_9XOAL0.js → ko-DlGbo3kV.js} +3 -3
- package/dist/assets/main-BREqmQUm.js +169 -0
- package/dist/assets/{main-Sd4Auh_x.js → main-BodZWzei.js} +2 -2
- package/dist/assets/main-qSeeJRW1.css +1 -0
- package/dist/assets/{markdownHeightCache-B5VSLl79.js → markdownHeightCache-CMoWScss.js} +1 -1
- package/dist/assets/{mcpQueries-BYNrkehM.js → mcpQueries-nScpWEqu.js} +1 -1
- package/dist/assets/{miniChat-Hp9bNmS0.js → miniChat-Ca-kdWf0.js} +2 -2
- package/dist/assets/{mobile-fBLEOx9_.js → mobile-BWi6F8Lj.js} +2 -2
- package/dist/assets/multirun-BdSrR6jN.js +3 -0
- package/dist/assets/persistence-B3_CPgm1.js +3 -0
- package/dist/assets/{pl-Dst9Wk53.js → pl-DjUsS8Ei.js} +3 -3
- package/dist/assets/platform-Bxdc4SbV.js +6 -0
- package/dist/assets/projectMeta-CIKLMweH.js +1 -0
- package/dist/assets/{pt-BR-Dq394BPb.js → pt-BR-B8sV7qo1.js} +3 -3
- package/dist/assets/{renderElectronMiniChatApp-D1xipCO5.js → renderElectronMiniChatApp-DtYCIW4n.js} +2 -2
- package/dist/assets/renderMobileApp-D47vojsP.js +28 -0
- package/dist/assets/{runtimeConfig-pTYamMLy.js → runtimeConfig-u9DrZmkS.js} +1 -1
- package/dist/assets/runtimeEndpointReset-Bn4vJ-gl.js +148 -0
- package/dist/assets/runtimeEndpointReset-D1_Y5I4F.css +1 -0
- package/dist/assets/{sessionChangeSummary-DcnZyuWt.js → sessionChangeSummary-C0OxmHed.js} +2 -2
- package/dist/assets/{sessionLookup-B3t-FerN.js → sessionLookup-D1NHRgWf.js} +1 -1
- package/dist/assets/{sortable-tabs-strip-B0sZ7mm6.js → sortable-tabs-strip-Cp5i5hI-.js} +1 -1
- package/dist/assets/{store-D8fmTWK3.js → store-BTqPn8Uq.js} +3 -3
- package/dist/assets/{terminalApi-CJyWCuc8.js → terminalApi-CqyW3_W3.js} +1 -1
- package/dist/assets/{textarea-B5GMntHk.js → textarea-jEbc6gqf.js} +1 -1
- package/dist/assets/{tooltip-C64SdRvR.js → tooltip-DKYT7xYe.js} +1 -1
- package/dist/assets/{uk-8XAR75Qh.js → uk-Bh7HXyLp.js} +3 -3
- package/dist/assets/{url-DF1NFwt_.js → url-hhmiocZY.js} +1 -1
- package/dist/assets/{useAppFontEffects-BQklk6XB.js → useAppFontEffects-BBX1KjLV.js} +53 -53
- package/dist/assets/{useDesktopWindowControlsLayout-CrzYwZDQ.js → useDesktopWindowControlsLayout-uKYzUX-m.js} +1 -1
- package/dist/assets/{useEffectiveDirectory-Ddx7sZ5A.js → useEffectiveDirectory-BOolveMp.js} +1 -1
- package/dist/assets/{useFontPreferences-CrteQiAp.js → useFontPreferences-BMJQP6t6.js} +1 -1
- package/dist/assets/useMobileNavigationStore-CC8QT7VC.js +415 -0
- package/dist/assets/useSessionAutoCleanup-CydMAlLf.js +2 -0
- package/dist/assets/{useWorkerHighlightedLines-VOtVUGkO.js → useWorkerHighlightedLines-BF1ClmMe.js} +1 -1
- package/dist/assets/vendor-opencode-client-MF2GBrFx.js +12 -0
- package/dist/assets/{zh-CN-B-ILfrqC.js → zh-CN-Uzia0gk4.js} +3 -3
- package/dist/assets/{zh-TW-5HeTVP4z.js → zh-TW-CiI7DLdB.js} +3 -3
- package/dist/index.html +8 -8
- package/dist/mini-chat.html +8 -8
- package/dist/mobile.html +8 -8
- package/package.json +3 -3
- package/server/index.js +2 -20
- package/server/lib/git/DOCUMENTATION.md +1 -0
- package/server/lib/git/routes.js +7 -4
- package/server/lib/git/routes.test.js +34 -0
- package/server/lib/git/service.js +3 -1
- package/server/lib/llm/DOCUMENTATION.md +6 -4
- package/server/lib/llm/generate.js +8 -9
- package/server/lib/llm/generate.test.js +33 -6
- package/server/lib/opencode/DOCUMENTATION.md +24 -20
- package/server/lib/opencode/agents.js +13 -22
- package/server/lib/opencode/auth-state-runtime.js +3 -3
- package/server/lib/opencode/auth-state-runtime.test.js +59 -0
- package/server/lib/opencode/auth.d.ts +13 -0
- package/server/lib/opencode/auth.js +74 -64
- package/server/lib/opencode/auth.test.js +57 -0
- package/server/lib/opencode/commands.js +1 -1
- package/server/lib/opencode/config-entities-layering.test.js +72 -0
- package/server/lib/opencode/config-entity-routes.js +9 -122
- package/server/lib/opencode/config-entity-routes.test.js +7 -11
- package/server/lib/opencode/config-files.d.ts +17 -0
- package/server/lib/opencode/config-files.js +187 -0
- package/server/lib/opencode/config-files.test.js +201 -0
- package/server/lib/opencode/core-routes.js +0 -1
- package/server/lib/opencode/core-routes.test.js +0 -15
- package/server/lib/opencode/credential-boundary.d.ts +1 -0
- package/server/lib/opencode/credential-boundary.js +11 -0
- package/server/lib/opencode/ensure-cli.js +4 -1
- package/server/lib/opencode/env-config.js +4 -1
- package/server/lib/opencode/env-config.test.js +28 -0
- package/server/lib/opencode/env-runtime.test.js +2 -1
- package/server/lib/opencode/feature-routes-runtime.js +9 -7
- package/server/lib/opencode/hmr-state-runtime.js +4 -4
- package/server/lib/opencode/lifecycle.js +1 -49
- package/server/lib/opencode/lifecycle.test.js +30 -4
- package/server/lib/opencode/location-release.d.ts +8 -0
- package/server/lib/opencode/location-release.js +32 -0
- package/server/lib/opencode/location-release.test.js +43 -0
- package/server/lib/opencode/mcp.js +1 -1
- package/server/lib/opencode/opencode2-pin.js +2 -1
- package/server/lib/opencode/owned-runtime-upgrade.test.js +5 -5
- package/server/lib/opencode/plugins.js +12 -60
- package/server/lib/opencode/providers.js +6 -3
- package/server/lib/opencode/proxy.js +7 -0
- package/server/lib/opencode/proxy.test.js +13 -0
- package/server/lib/opencode/routes.js +5 -5
- package/server/lib/opencode/routes.test.js +18 -18
- package/server/lib/opencode/runtime-contract.js +3 -2
- package/server/lib/opencode/runtime-contract.test.js +12 -12
- package/server/lib/opencode/shared.js +6 -175
- package/server/lib/opencode/upgrade-screen.js +4 -11
- package/server/lib/opencode/upgrade-screen.test.js +25 -25
- package/server/lib/opencode/version-alignment.test.js +40 -0
- package/server/lib/quota/DOCUMENTATION.md +9 -0
- package/server/lib/quota/provider-credentials.test.js +29 -0
- package/server/lib/quota/providers/claude.js +4 -4
- package/server/lib/quota/providers/codex.js +4 -4
- package/server/lib/quota/providers/codex.test.js +1 -1
- package/server/lib/quota/providers/copilot.js +5 -5
- package/server/lib/quota/providers/google/auth.js +3 -3
- package/server/lib/quota/providers/google/index.js +2 -2
- package/server/lib/quota/providers/index.js +3 -7
- package/server/lib/quota/providers/index.test.js +2 -2
- package/server/lib/quota/providers/kimi.js +4 -4
- package/server/lib/quota/providers/minimax-shared.js +4 -4
- package/server/lib/quota/providers/nanogpt.js +4 -4
- package/server/lib/quota/providers/openai.js +4 -4
- package/server/lib/quota/providers/openrouter.js +4 -4
- package/server/lib/quota/providers/wafer.js +4 -4
- package/server/lib/quota/providers/zai.js +4 -4
- package/server/lib/quota/providers/zhipuai-coding-plan.js +6 -20
- package/server/lib/quota/routes.js +1 -1
- package/server/lib/scheduled-tasks/DOCUMENTATION.md +1 -0
- package/server/lib/scheduled-tasks/runtime.js +2 -3
- package/server/lib/session-index/DOCUMENTATION.md +7 -0
- package/server/lib/session-index/event-ingest.js +13 -0
- package/server/lib/session-index/event-ingest.test.js +7 -0
- package/server/lib/session-index/service.js +45 -0
- package/server/lib/session-index/service.test.js +47 -0
- package/server/lib/tts/DOCUMENTATION.md +7 -5
- package/server/lib/tts/credential-source.test.js +23 -0
- package/server/lib/tts/routes.js +2 -2
- package/server/lib/tts/service.js +16 -37
- package/dist/assets/MobileSurface-ocVWenHA.js +0 -2
- package/dist/assets/SettingsView-CIZz40Jg.js +0 -25
- package/dist/assets/fileMentionTouchSelection-Bj67pPo7.js +0 -3
- package/dist/assets/insertionBoundaries-nzs1Q3YR.js +0 -1
- package/dist/assets/main-D2BzpnUt.css +0 -1
- package/dist/assets/main-DjilfYN7.js +0 -169
- package/dist/assets/multirun-D7W7oCj4.js +0 -4
- package/dist/assets/persistence-CMWO3jFo.js +0 -3
- package/dist/assets/platform-B0p43squ.js +0 -6
- package/dist/assets/projectMeta-DrUKrYt0.js +0 -3
- package/dist/assets/renderMobileApp-DSk7Qs-_.js +0 -28
- package/dist/assets/runtimeEndpointReset-B5HEMuiB.js +0 -149
- package/dist/assets/useMobileNavigationStore-i4cXNoOf.js +0 -414
- package/dist/assets/useSessionAutoCleanup-Dzd0fGSe.js +0 -2
- package/dist/assets/vendor-opencode-client-C12gfjag.js +0 -12
- package/server/lib/opencode/snippets.js +0 -242
- package/server/lib/opencode/snippets.test.js +0 -97
|
@@ -14,7 +14,7 @@ import {
|
|
|
14
14
|
asNonEmptyString,
|
|
15
15
|
toTimestamp
|
|
16
16
|
} from '../../utils/index.js';
|
|
17
|
-
import {
|
|
17
|
+
import { readOpenCodeCredentials } from '../../../opencode/auth.js';
|
|
18
18
|
import { parseGoogleRefreshToken } from './transforms.js';
|
|
19
19
|
|
|
20
20
|
const ANTIGRAVITY_GOOGLE_CLIENT_ID =
|
|
@@ -90,8 +90,8 @@ const resolveAntigravityAuth = () => {
|
|
|
90
90
|
return null;
|
|
91
91
|
};
|
|
92
92
|
|
|
93
|
-
export const resolveGoogleAuthSources = () => {
|
|
94
|
-
const auth =
|
|
93
|
+
export const resolveGoogleAuthSources = async () => {
|
|
94
|
+
const auth = await readOpenCodeCredentials();
|
|
95
95
|
const sources = [];
|
|
96
96
|
|
|
97
97
|
const geminiAuth = resolveGeminiCliAuth(auth);
|
|
@@ -17,10 +17,10 @@ export const providerId = 'google';
|
|
|
17
17
|
export const providerName = 'Google';
|
|
18
18
|
export const aliases = ['google', 'google.oauth'];
|
|
19
19
|
|
|
20
|
-
export const isConfigured = () => resolveGoogleAuthSources().length > 0;
|
|
20
|
+
export const isConfigured = async () => (await resolveGoogleAuthSources()).length > 0;
|
|
21
21
|
|
|
22
22
|
export const fetchGoogleQuota = async () => {
|
|
23
|
-
const authSources = resolveGoogleAuthSources();
|
|
23
|
+
const authSources = await resolveGoogleAuthSources();
|
|
24
24
|
if (!authSources.length) {
|
|
25
25
|
return buildResult({
|
|
26
26
|
providerId,
|
|
@@ -130,16 +130,12 @@ const registry = {
|
|
|
130
130
|
}
|
|
131
131
|
};
|
|
132
132
|
|
|
133
|
-
export const listConfiguredQuotaProviders = () => {
|
|
133
|
+
export const listConfiguredQuotaProviders = async () => {
|
|
134
134
|
const configured = [];
|
|
135
135
|
|
|
136
136
|
for (const [id, provider] of Object.entries(registry)) {
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
configured.push(id);
|
|
140
|
-
}
|
|
141
|
-
} catch {
|
|
142
|
-
// Ignore provider-specific config errors in list API.
|
|
137
|
+
if (await provider.isConfigured()) {
|
|
138
|
+
configured.push(id);
|
|
143
139
|
}
|
|
144
140
|
}
|
|
145
141
|
|
|
@@ -11,7 +11,7 @@ describe('quota provider registry', () => {
|
|
|
11
11
|
expect(typeof google.resolveGoogleAuthSources).toBe('function');
|
|
12
12
|
});
|
|
13
13
|
|
|
14
|
-
it('
|
|
15
|
-
expect(
|
|
14
|
+
it('reports unavailable credentials instead of an empty configured-provider list', async () => {
|
|
15
|
+
await expect(listConfiguredQuotaProviders()).rejects.toThrow('Unable to read OpenCode credentials');
|
|
16
16
|
});
|
|
17
17
|
});
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { readOpenCodeCredentials } from '../../opencode/auth.js';
|
|
2
2
|
import {
|
|
3
3
|
getAuthEntry,
|
|
4
4
|
normalizeAuthEntry,
|
|
@@ -14,14 +14,14 @@ export const providerId = 'kimi-for-coding';
|
|
|
14
14
|
export const providerName = 'Kimi for Coding';
|
|
15
15
|
const aliases = ['kimi-for-coding', 'kimi'];
|
|
16
16
|
|
|
17
|
-
export const isConfigured = () => {
|
|
18
|
-
const auth =
|
|
17
|
+
export const isConfigured = async () => {
|
|
18
|
+
const auth = await readOpenCodeCredentials();
|
|
19
19
|
const entry = normalizeAuthEntry(getAuthEntry(auth, aliases));
|
|
20
20
|
return Boolean(entry?.key || entry?.token);
|
|
21
21
|
};
|
|
22
22
|
|
|
23
23
|
export const fetchQuota = async () => {
|
|
24
|
-
const auth =
|
|
24
|
+
const auth = await readOpenCodeCredentials();
|
|
25
25
|
const entry = normalizeAuthEntry(getAuthEntry(auth, aliases));
|
|
26
26
|
const apiKey = entry?.key ?? entry?.token;
|
|
27
27
|
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { readOpenCodeCredentials } from '../../opencode/auth.js';
|
|
2
2
|
import {
|
|
3
3
|
getAuthEntry,
|
|
4
4
|
normalizeAuthEntry,
|
|
@@ -138,14 +138,14 @@ const calculateUsage = (model, isTokenPlan) => {
|
|
|
138
138
|
};
|
|
139
139
|
|
|
140
140
|
export const createMiniMaxCodingPlanProvider = ({ providerId, providerName, aliases, tokenPlanUrl, codingPlanUrl }) => {
|
|
141
|
-
const isConfigured = () => {
|
|
142
|
-
const auth =
|
|
141
|
+
const isConfigured = async () => {
|
|
142
|
+
const auth = await readOpenCodeCredentials();
|
|
143
143
|
const entry = normalizeAuthEntry(getAuthEntry(auth, aliases));
|
|
144
144
|
return Boolean(entry?.key || entry?.token);
|
|
145
145
|
};
|
|
146
146
|
|
|
147
147
|
const fetchQuota = async () => {
|
|
148
|
-
const auth =
|
|
148
|
+
const auth = await readOpenCodeCredentials();
|
|
149
149
|
const entry = normalizeAuthEntry(getAuthEntry(auth, aliases));
|
|
150
150
|
const apiKey = entry?.key ?? entry?.token;
|
|
151
151
|
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { readOpenCodeCredentials } from '../../opencode/auth.js';
|
|
2
2
|
import {
|
|
3
3
|
getAuthEntry,
|
|
4
4
|
normalizeAuthEntry,
|
|
@@ -14,14 +14,14 @@ export const providerId = 'nano-gpt';
|
|
|
14
14
|
export const providerName = 'NanoGPT';
|
|
15
15
|
const aliases = ['nano-gpt', 'nanogpt', 'nano_gpt'];
|
|
16
16
|
|
|
17
|
-
export const isConfigured = () => {
|
|
18
|
-
const auth =
|
|
17
|
+
export const isConfigured = async () => {
|
|
18
|
+
const auth = await readOpenCodeCredentials();
|
|
19
19
|
const entry = normalizeAuthEntry(getAuthEntry(auth, aliases));
|
|
20
20
|
return Boolean(entry?.key || entry?.token);
|
|
21
21
|
};
|
|
22
22
|
|
|
23
23
|
export const fetchQuota = async () => {
|
|
24
|
-
const auth =
|
|
24
|
+
const auth = await readOpenCodeCredentials();
|
|
25
25
|
const entry = normalizeAuthEntry(getAuthEntry(auth, aliases));
|
|
26
26
|
const apiKey = entry?.key ?? entry?.token;
|
|
27
27
|
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { readOpenCodeCredentials } from '../../opencode/auth.js';
|
|
2
2
|
import {
|
|
3
3
|
getAuthEntry,
|
|
4
4
|
normalizeAuthEntry,
|
|
@@ -12,14 +12,14 @@ const providerId = 'openai';
|
|
|
12
12
|
const providerName = 'OpenAI';
|
|
13
13
|
const aliases = ['openai', 'codex', 'chatgpt'];
|
|
14
14
|
|
|
15
|
-
const isConfigured = () => {
|
|
16
|
-
const auth =
|
|
15
|
+
const isConfigured = async () => {
|
|
16
|
+
const auth = await readOpenCodeCredentials();
|
|
17
17
|
const entry = normalizeAuthEntry(getAuthEntry(auth, aliases));
|
|
18
18
|
return Boolean(entry?.access || entry?.token);
|
|
19
19
|
};
|
|
20
20
|
|
|
21
21
|
export const fetchQuota = async () => {
|
|
22
|
-
const auth =
|
|
22
|
+
const auth = await readOpenCodeCredentials();
|
|
23
23
|
const entry = normalizeAuthEntry(getAuthEntry(auth, aliases));
|
|
24
24
|
const accessToken = entry?.access ?? entry?.token;
|
|
25
25
|
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { readOpenCodeCredentials } from '../../opencode/auth.js';
|
|
2
2
|
import {
|
|
3
3
|
getAuthEntry,
|
|
4
4
|
normalizeAuthEntry,
|
|
@@ -12,14 +12,14 @@ export const providerId = 'openrouter';
|
|
|
12
12
|
export const providerName = 'OpenRouter';
|
|
13
13
|
const aliases = ['openrouter'];
|
|
14
14
|
|
|
15
|
-
export const isConfigured = () => {
|
|
16
|
-
const auth =
|
|
15
|
+
export const isConfigured = async () => {
|
|
16
|
+
const auth = await readOpenCodeCredentials();
|
|
17
17
|
const entry = normalizeAuthEntry(getAuthEntry(auth, aliases));
|
|
18
18
|
return Boolean(entry?.key || entry?.token);
|
|
19
19
|
};
|
|
20
20
|
|
|
21
21
|
export const fetchQuota = async () => {
|
|
22
|
-
const auth =
|
|
22
|
+
const auth = await readOpenCodeCredentials();
|
|
23
23
|
const entry = normalizeAuthEntry(getAuthEntry(auth, aliases));
|
|
24
24
|
const apiKey = entry?.key ?? entry?.token;
|
|
25
25
|
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { readOpenCodeCredentials } from '../../opencode/auth.js';
|
|
2
2
|
import {
|
|
3
3
|
getAuthEntry,
|
|
4
4
|
normalizeAuthEntry,
|
|
@@ -17,14 +17,14 @@ const aliases = ['wafer', 'wafer-ai', 'wafer_ai', 'wafer.ai'];
|
|
|
17
17
|
const WAFER_QUOTA_URL = 'https://pass.wafer.ai/v1/inference/quota';
|
|
18
18
|
const WAFER_WINDOW_SECONDS = 5 * 3600;
|
|
19
19
|
|
|
20
|
-
export const isConfigured = () => {
|
|
21
|
-
const auth =
|
|
20
|
+
export const isConfigured = async () => {
|
|
21
|
+
const auth = await readOpenCodeCredentials();
|
|
22
22
|
const entry = normalizeAuthEntry(getAuthEntry(auth, aliases));
|
|
23
23
|
return Boolean(entry?.key || entry?.token);
|
|
24
24
|
};
|
|
25
25
|
|
|
26
26
|
export const fetchQuota = async () => {
|
|
27
|
-
const auth =
|
|
27
|
+
const auth = await readOpenCodeCredentials();
|
|
28
28
|
const entry = normalizeAuthEntry(getAuthEntry(auth, aliases));
|
|
29
29
|
const apiKey = entry?.key ?? entry?.token;
|
|
30
30
|
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { readOpenCodeCredentials } from '../../opencode/auth.js';
|
|
2
2
|
import {
|
|
3
3
|
getAuthEntry,
|
|
4
4
|
normalizeAuthEntry,
|
|
@@ -15,14 +15,14 @@ export const providerId = 'zai-coding-plan';
|
|
|
15
15
|
export const providerName = 'z.ai';
|
|
16
16
|
const aliases = ['zai-coding-plan', 'zai', 'z.ai'];
|
|
17
17
|
|
|
18
|
-
export const isConfigured = () => {
|
|
19
|
-
const auth =
|
|
18
|
+
export const isConfigured = async () => {
|
|
19
|
+
const auth = await readOpenCodeCredentials();
|
|
20
20
|
const entry = normalizeAuthEntry(getAuthEntry(auth, aliases));
|
|
21
21
|
return Boolean(entry?.key || entry?.token);
|
|
22
22
|
};
|
|
23
23
|
|
|
24
24
|
export const fetchQuota = async () => {
|
|
25
|
-
const auth =
|
|
25
|
+
const auth = await readOpenCodeCredentials();
|
|
26
26
|
const entry = normalizeAuthEntry(getAuthEntry(auth, aliases));
|
|
27
27
|
const apiKey = entry?.key ?? entry?.token;
|
|
28
28
|
|
|
@@ -25,8 +25,7 @@
|
|
|
25
25
|
* @property {number} [nextResetTime]
|
|
26
26
|
* @property {Array<{modelCode: string, usage: number}>} [usageDetails]
|
|
27
27
|
*/
|
|
28
|
-
import {
|
|
29
|
-
import { readConfigLayers } from '../../opencode/shared.js';
|
|
28
|
+
import { readOpenCodeCredentials } from '../../opencode/auth.js';
|
|
30
29
|
import {
|
|
31
30
|
getAuthEntry,
|
|
32
31
|
normalizeAuthEntry,
|
|
@@ -40,8 +39,8 @@ export const providerId = 'zhipuai-coding-plan';
|
|
|
40
39
|
export const providerName = 'Zhipu AI Coding Plan';
|
|
41
40
|
const aliases = ['zhipuai-coding-plan', 'zhipuai', 'zhipu'];
|
|
42
41
|
|
|
43
|
-
function getApiKey() {
|
|
44
|
-
const auth =
|
|
42
|
+
async function getApiKey() {
|
|
43
|
+
const auth = await readOpenCodeCredentials();
|
|
45
44
|
const entry = normalizeAuthEntry(getAuthEntry(auth, aliases));
|
|
46
45
|
const apiKeyFromAuth = entry?.key ?? entry?.token;
|
|
47
46
|
|
|
@@ -49,28 +48,15 @@ function getApiKey() {
|
|
|
49
48
|
return apiKeyFromAuth;
|
|
50
49
|
}
|
|
51
50
|
|
|
52
|
-
try {
|
|
53
|
-
const { mergedConfig } = readConfigLayers();
|
|
54
|
-
|
|
55
|
-
for (const alias of aliases) {
|
|
56
|
-
const providerConfig = mergedConfig?.provider?.[alias];
|
|
57
|
-
if (providerConfig?.options?.apiKey) {
|
|
58
|
-
return providerConfig.options.apiKey;
|
|
59
|
-
}
|
|
60
|
-
}
|
|
61
|
-
} catch {
|
|
62
|
-
// Ignore config read errors; the provider will be treated as not configured.
|
|
63
|
-
}
|
|
64
|
-
|
|
65
51
|
return null;
|
|
66
52
|
}
|
|
67
53
|
|
|
68
|
-
export const isConfigured = () => {
|
|
69
|
-
return Boolean(getApiKey());
|
|
54
|
+
export const isConfigured = async () => {
|
|
55
|
+
return Boolean(await getApiKey());
|
|
70
56
|
};
|
|
71
57
|
|
|
72
58
|
export const fetchQuota = async () => {
|
|
73
|
-
const apiKey = getApiKey();
|
|
59
|
+
const apiKey = await getApiKey();
|
|
74
60
|
|
|
75
61
|
if (!apiKey) {
|
|
76
62
|
return buildResult({
|
|
@@ -28,7 +28,7 @@ export function registerQuotaRoutes(app, { getQuotaProviders }) {
|
|
|
28
28
|
app.get('/api/quota/providers', async (_req, res) => {
|
|
29
29
|
try {
|
|
30
30
|
const { listConfiguredQuotaProviders } = await getQuotaProviders();
|
|
31
|
-
res.json({ providers: listConfiguredQuotaProviders() });
|
|
31
|
+
res.json({ providers: await listConfiguredQuotaProviders() });
|
|
32
32
|
} catch (error) {
|
|
33
33
|
console.error('Failed to list quota providers:', error);
|
|
34
34
|
res.status(500).json({ error: error.message || 'Failed to list quota providers' });
|
|
@@ -8,6 +8,7 @@ Server-owned scheduled task runtime and routes for OpenChamber-only automation.
|
|
|
8
8
|
- Runtime orchestration and execution is owned by this module.
|
|
9
9
|
- Durable run history is owned by an independent SQLite store in this module.
|
|
10
10
|
- This module is OpenChamber feature logic; it is intentionally separate from OpenCode proxy/runtime internals.
|
|
11
|
+
- Prompt and goal execution preserve hash-prefixed text literally; no snippet discovery or expansion occurs before dispatch.
|
|
11
12
|
|
|
12
13
|
## Files
|
|
13
14
|
|
|
@@ -2,7 +2,6 @@ import crypto from 'node:crypto';
|
|
|
2
2
|
import { DateTime } from 'luxon';
|
|
3
3
|
import parser from 'cron-parser';
|
|
4
4
|
import { makeOpenCodeV2Client } from '../opencode/v2-client.js';
|
|
5
|
-
import { expandSnippets } from '../opencode/snippets.js';
|
|
6
5
|
import {
|
|
7
6
|
getSessionGoalCapability,
|
|
8
7
|
isSessionGoalSupported,
|
|
@@ -858,7 +857,7 @@ export const createScheduledTasksRuntime = (deps) => {
|
|
|
858
857
|
};
|
|
859
858
|
|
|
860
859
|
const buildPromptText = (task, projectPath) => {
|
|
861
|
-
const promptText =
|
|
860
|
+
const promptText = task.execution.prompt;
|
|
862
861
|
if (!task.execution.goalEnabled) {
|
|
863
862
|
return promptText;
|
|
864
863
|
}
|
|
@@ -883,7 +882,7 @@ export const createScheduledTasksRuntime = (deps) => {
|
|
|
883
882
|
// back to an inline objective so parseGoalMetadata still accepts the
|
|
884
883
|
// record. Oversized prompts are distilled into audit criteria by the
|
|
885
884
|
// small model; on distill failure a head+tail excerpt keeps intent.
|
|
886
|
-
let objectiveText =
|
|
885
|
+
let objectiveText = task.execution.prompt;
|
|
887
886
|
if (objectiveText.length > 5000) {
|
|
888
887
|
let distilled = null;
|
|
889
888
|
try {
|
|
@@ -1,5 +1,12 @@
|
|
|
1
1
|
# Session Index Runtime
|
|
2
2
|
|
|
3
|
+
Native `session.renamed` updates the existing runtime-scoped summary's title and
|
|
4
|
+
update clock directly, publishing through the normal index revision path. It
|
|
5
|
+
preserves activity ordering, pins and execution status; older/duplicate events
|
|
6
|
+
and unknown sessions are no-ops. Partial rename payloads never fabricate rows.
|
|
7
|
+
|
|
8
|
+
Native `session.moved` uses `data.location.directory`, not the old event-envelope directory. The transactional `move` operation retains summary activity/status and independent pin membership, maintains the newest-20 destination bound, and installs a runtime-scoped event-clock fence against delayed source-directory upserts/pages. Duplicate/older moves are no-ops. Unknown sessions are not fabricated from the partial move payload.
|
|
9
|
+
|
|
3
10
|
Every OpenChamber Web Server enables `session-index.sqlite` in its data
|
|
4
11
|
directory by default. Electron injects its user-data path explicitly.
|
|
5
12
|
`sessionIndexDbPath` and `OPENCHAMBER_SESSION_INDEX_DB_PATH` override that
|
|
@@ -36,6 +36,19 @@ export const applySessionIndexEvent = (service, event, observedAt = Date.now(),
|
|
|
36
36
|
|
|
37
37
|
const baseType = normalizeSessionEventType(payload.type);
|
|
38
38
|
|
|
39
|
+
if (baseType === 'session.renamed') {
|
|
40
|
+
if (isHostReady && !isHostReady()) return false;
|
|
41
|
+
const data = payload.data ?? properties;
|
|
42
|
+
return service.rename(data.sessionID, data.title, payload.created ?? properties.eventCreated ?? observedAt);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
if (baseType === 'session.moved') {
|
|
46
|
+
const data = payload.data ?? properties;
|
|
47
|
+
return typeof data.sessionID === 'string' && typeof data.location?.directory === 'string'
|
|
48
|
+
? service.move(data.sessionID, data.location.directory, typeof payload.created === 'number' ? payload.created : observedAt, payload.durable?.seq)
|
|
49
|
+
: false;
|
|
50
|
+
}
|
|
51
|
+
|
|
39
52
|
if (baseType === 'session.created' || baseType === 'session.updated') {
|
|
40
53
|
// Until Host metadata is ready, do not write session lifecycle into the
|
|
41
54
|
// index (would bake unprojected upstream archive). Transcript paths use
|
|
@@ -3,6 +3,13 @@ import { describe, expect, it, vi } from 'vitest';
|
|
|
3
3
|
import { applySessionIndexEvent } from './event-ingest.js';
|
|
4
4
|
|
|
5
5
|
describe('session index event ingest', () => {
|
|
6
|
+
it('takes moved ownership from native data rather than the old event envelope directory', () => {
|
|
7
|
+
const service = { move: vi.fn(() => true) };
|
|
8
|
+
expect(applySessionIndexEvent(service, { directory: '/old', payload: {
|
|
9
|
+
type: 'session.moved', created: 20, data: { sessionID: 'ses_move', location: { directory: '/new' }, projectID: 'project' },
|
|
10
|
+
} }, 30)).toBe(true);
|
|
11
|
+
expect(service.move).toHaveBeenCalledWith('ses_move', '/new', 20, undefined);
|
|
12
|
+
});
|
|
6
13
|
it('writes session summaries, user activity, and status to the service', () => {
|
|
7
14
|
const service = {
|
|
8
15
|
upsertAndReportChange: vi.fn(() => true),
|
|
@@ -277,6 +277,30 @@ export const createSessionIndexService = ({ dbPath, getRuntimeConfig = () => nul
|
|
|
277
277
|
WHERE runtime_key = ? AND session_id = ?
|
|
278
278
|
`);
|
|
279
279
|
|
|
280
|
+
const movedLocations = new Map();
|
|
281
|
+
const isStaleLocation = (session, directory) => {
|
|
282
|
+
const moved = movedLocations.get(`${runtimeKey()}:${session?.id}`);
|
|
283
|
+
return moved && moved.directory !== normalizeDirectory(directory) && toTimestamp(session?.time?.updated) <= moved.at;
|
|
284
|
+
};
|
|
285
|
+
const move = db.transaction((sessionID, directory, at = Date.now(), sequence) => {
|
|
286
|
+
directory = normalizeDirectory(directory);
|
|
287
|
+
if (!sessionID || !directory) return false;
|
|
288
|
+
const key = runtimeKey();
|
|
289
|
+
const fenceKey = `${key}:${sessionID}`;
|
|
290
|
+
const previous = movedLocations.get(fenceKey);
|
|
291
|
+
if (previous && (sequence !== undefined && previous.sequence !== undefined
|
|
292
|
+
? sequence <= previous.sequence
|
|
293
|
+
: at < previous.at || (at === previous.at && previous.directory === directory))) return false;
|
|
294
|
+
movedLocations.set(fenceKey, { directory, at, sequence });
|
|
295
|
+
touchExistingDirectory.run({ runtimeKey: key, directory, lastAccessedAt: at });
|
|
296
|
+
const roots = db.prepare('UPDATE OR REPLACE session_summary SET directory = ? WHERE runtime_key = ? AND session_id = ? AND directory != ?').run(directory, key, sessionID, directory);
|
|
297
|
+
const children = db.prepare('UPDATE OR REPLACE session_child SET directory = ? WHERE runtime_key = ? AND session_id = ? AND directory != ?').run(directory, key, sessionID, directory);
|
|
298
|
+
db.prepare(`DELETE FROM session_summary WHERE runtime_key = ? AND directory = ? AND session_id IN (
|
|
299
|
+
SELECT session_id FROM session_summary WHERE runtime_key = ? AND directory = ? ORDER BY activity_updated_at DESC, session_id DESC LIMIT -1 OFFSET ?
|
|
300
|
+
)`).run(key, directory, key, directory, MAX_ROOT_SESSIONS);
|
|
301
|
+
return roots.changes > 0 || children.changes > 0;
|
|
302
|
+
});
|
|
303
|
+
|
|
280
304
|
const replaceDirectoryRows = ({ directory, sessions, cursor, hasMore, fullSync = true, now = Date.now() }) => {
|
|
281
305
|
const normalizedDirectory = normalizeDirectory(directory);
|
|
282
306
|
if (!normalizedDirectory) throw new Error('directory is required');
|
|
@@ -289,6 +313,7 @@ export const createSessionIndexService = ({ dbPath, getRuntimeConfig = () => nul
|
|
|
289
313
|
// stale unarchived cache cannot resurrect them.
|
|
290
314
|
const summaries = Array.isArray(sessions)
|
|
291
315
|
? sessions
|
|
316
|
+
.filter((session) => !isStaleLocation(session, normalizedDirectory))
|
|
292
317
|
.map((session) => toSummary(session, normalizedDirectory))
|
|
293
318
|
.filter((summary) => summary && !summary.archivedAt)
|
|
294
319
|
.slice(0, MAX_ROOT_SESSIONS)
|
|
@@ -328,6 +353,7 @@ export const createSessionIndexService = ({ dbPath, getRuntimeConfig = () => nul
|
|
|
328
353
|
});
|
|
329
354
|
|
|
330
355
|
const upsertMutation = db.transaction((session, now = Date.now(), options = {}) => {
|
|
356
|
+
if (isStaleLocation(session, session?.directory ?? session?.location?.directory)) return { accepted: false, changed: false };
|
|
331
357
|
if (!isVisibleSession(session)) {
|
|
332
358
|
const changed = remove(session?.id);
|
|
333
359
|
return { accepted: changed, changed };
|
|
@@ -517,6 +543,23 @@ export const createSessionIndexService = ({ dbPath, getRuntimeConfig = () => nul
|
|
|
517
543
|
return result.changes > 0;
|
|
518
544
|
};
|
|
519
545
|
|
|
546
|
+
const rename = (sessionID, title, observedAt) => {
|
|
547
|
+
if (typeof sessionID !== 'string' || !sessionID || typeof title !== 'string') return false;
|
|
548
|
+
const timestamp = toTimestamp(observedAt);
|
|
549
|
+
if (!timestamp) return false;
|
|
550
|
+
const key = runtimeKey();
|
|
551
|
+
if (HIDDEN_SESSION_TITLES.has(title)) {
|
|
552
|
+
const current = db.prepare('SELECT updated_at FROM session_summary WHERE runtime_key = ? AND session_id = ?').get(key, sessionID);
|
|
553
|
+
return current && current.updated_at <= timestamp ? remove(sessionID) : false;
|
|
554
|
+
}
|
|
555
|
+
const result = db.prepare(`
|
|
556
|
+
UPDATE session_summary SET title = ?, updated_at = ?
|
|
557
|
+
WHERE runtime_key = ? AND session_id = ? AND updated_at <= ?
|
|
558
|
+
AND (title != ? OR updated_at != ?)
|
|
559
|
+
`).run(title, timestamp, key, sessionID, timestamp, title, timestamp);
|
|
560
|
+
return result.changes > 0;
|
|
561
|
+
};
|
|
562
|
+
|
|
520
563
|
const updateStatus = (sessionID, status, observedAt) => {
|
|
521
564
|
if (typeof sessionID !== 'string' || !sessionID) return false;
|
|
522
565
|
if (status !== 'busy' && status !== 'retry' && status !== 'idle') return false;
|
|
@@ -570,6 +613,8 @@ export const createSessionIndexService = ({ dbPath, getRuntimeConfig = () => nul
|
|
|
570
613
|
upsertAndReportChange,
|
|
571
614
|
touchActivity,
|
|
572
615
|
updateStatus,
|
|
616
|
+
rename,
|
|
617
|
+
move,
|
|
573
618
|
setPinned,
|
|
574
619
|
clearPinned,
|
|
575
620
|
remove,
|
|
@@ -5,6 +5,7 @@ import path from 'node:path';
|
|
|
5
5
|
import { afterEach, describe, expect, it } from 'vitest';
|
|
6
6
|
|
|
7
7
|
import { createSessionIndexService } from './service.js';
|
|
8
|
+
import { applySessionIndexEvent } from './event-ingest.js';
|
|
8
9
|
|
|
9
10
|
const tempDirectories = [];
|
|
10
11
|
|
|
@@ -31,6 +32,52 @@ afterEach(() => {
|
|
|
31
32
|
});
|
|
32
33
|
|
|
33
34
|
describe('Electron session index', () => {
|
|
35
|
+
it('ingests native renames without changing activity, status, pins or unrelated sessions', () => {
|
|
36
|
+
const runtimeRef = { value: 'http://runtime-a.test' };
|
|
37
|
+
const service = createService(runtimeRef);
|
|
38
|
+
service.upsert(session('ses_title', 10));
|
|
39
|
+
service.upsert(session('ses_other', 15));
|
|
40
|
+
service.setPinned('ses_title', 12);
|
|
41
|
+
service.updateStatus('ses_title', 'busy', 13);
|
|
42
|
+
const before = service.snapshot();
|
|
43
|
+
const rename = (title, created, sessionID = 'ses_title') => applySessionIndexEvent(service, {
|
|
44
|
+
payload: { type: 'session.renamed', created, data: { sessionID, title } },
|
|
45
|
+
});
|
|
46
|
+
expect(rename('Generated title', 20)).toBe(true);
|
|
47
|
+
const after = service.snapshot();
|
|
48
|
+
expect(after.directories[0].sessions.map((row) => row.id)).toEqual(before.directories[0].sessions.map((row) => row.id));
|
|
49
|
+
expect(service.findBySessionId('ses_title')).toMatchObject({ title: 'Generated title' });
|
|
50
|
+
expect(after.pinnedSessionIds).toEqual(before.pinnedSessionIds);
|
|
51
|
+
expect(after.directories[0].sessions.find((row) => row.id === 'ses_title').metadata).toEqual(
|
|
52
|
+
before.directories[0].sessions.find((row) => row.id === 'ses_title').metadata,
|
|
53
|
+
);
|
|
54
|
+
expect(rename('Generated title', 20)).toBe(false);
|
|
55
|
+
expect(rename('Stale title', 19)).toBe(false);
|
|
56
|
+
expect(rename('Unknown', 30, 'ses_missing')).toBe(false);
|
|
57
|
+
runtimeRef.value = 'http://runtime-b.test';
|
|
58
|
+
expect(rename('Other runtime', 30)).toBe(false);
|
|
59
|
+
service.close();
|
|
60
|
+
});
|
|
61
|
+
it('moves one authoritative identity, preserving pin/status and fencing delayed old-directory pages', () => {
|
|
62
|
+
const runtimeRef = { value: 'http://runtime-a.test' };
|
|
63
|
+
const service = createService(runtimeRef);
|
|
64
|
+
service.upsert(session('ses_move', 10, '/old'));
|
|
65
|
+
service.setPinned('ses_move', 12);
|
|
66
|
+
service.updateStatus('ses_move', 'retry', 15);
|
|
67
|
+
expect(service.move('ses_move', '/new', 20)).toBe(true);
|
|
68
|
+
expect(service.move('ses_move', '/new', 20)).toBe(false);
|
|
69
|
+
expect(service.move('ses_move', '/old', 19)).toBe(false);
|
|
70
|
+
service.replaceDirectory({ directory: '/old', sessions: [session('ses_move', 10, '/old')], now: 30 });
|
|
71
|
+
expect(service.upsertAndReportChange(session('ses_move', 10, '/old'), 40)).toBe(false);
|
|
72
|
+
const rows = service.snapshot().directories.flatMap((item) => item.sessions);
|
|
73
|
+
expect(rows).toHaveLength(1);
|
|
74
|
+
expect(rows[0]).toMatchObject({ id: 'ses_move', directory: '/new', metadata: { openchamber: { sessionStatus: { type: 'retry' } } } });
|
|
75
|
+
expect(service.snapshot().pinnedSessionIds).toContain('ses_move');
|
|
76
|
+
runtimeRef.value = 'http://runtime-b.test';
|
|
77
|
+
expect(service.upsert(session('ses_move', 1, '/old'))).toBe(true);
|
|
78
|
+
expect(service.findBySessionId('ses_move').directory).toBe('/old');
|
|
79
|
+
service.close();
|
|
80
|
+
});
|
|
34
81
|
it('stores one bounded root-session page transactionally', () => {
|
|
35
82
|
const runtimeRef = { value: 'http://runtime-a.test' };
|
|
36
83
|
const service = createService(runtimeRef);
|
|
@@ -43,7 +43,8 @@ This module provides server-side Text-to-Speech services using OpenAI's TTS API.
|
|
|
43
43
|
## TTSService methods
|
|
44
44
|
|
|
45
45
|
### `isAvailable()`
|
|
46
|
-
Returns
|
|
46
|
+
Returns a Promise of boolean after reading the current OpenCode selected credential.
|
|
47
|
+
Credential-source failure rejects; logout clears the previously constructed client.
|
|
47
48
|
|
|
48
49
|
### `generateSpeechStream(options)`
|
|
49
50
|
Generates speech and returns as a web stream for direct streaming to clients.
|
|
@@ -81,10 +82,11 @@ Returns object with:
|
|
|
81
82
|
Returns Buffer containing MP3 audio data.
|
|
82
83
|
|
|
83
84
|
## API key resolution
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
85
|
+
Explicit request API key/custom base URL remains supported. Otherwise the host
|
|
86
|
+
reads the current OpenCode credential API (`openai`, `codex`, `chatgpt` aliases),
|
|
87
|
+
using API `key` or OAuth `access`. There is no host-local environment/file fallback
|
|
88
|
+
that could substitute another account for a remote service. A credential read
|
|
89
|
+
failure propagates; selected-account changes replace the client and logout clears it.
|
|
88
90
|
|
|
89
91
|
## Usage in web server
|
|
90
92
|
The TTS module is used by `packages/web/server/index.js` for:
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { afterEach, expect, it } from 'vitest';
|
|
2
|
+
import { configureOpenCodeCredentials } from '../opencode/auth.js';
|
|
3
|
+
import { TTSService } from './service.js';
|
|
4
|
+
|
|
5
|
+
afterEach(() => configureOpenCodeCredentials(null));
|
|
6
|
+
|
|
7
|
+
it('TTS clears its old client after logout and obtains a new client after account switching', async () => {
|
|
8
|
+
let token = 'first';
|
|
9
|
+
configureOpenCodeCredentials(() => ({ credential: { list: async () => token ? [{ id: 'selected', integrationID: 'openai', active: true, value: { type: 'key', key: token } }] : [] } }));
|
|
10
|
+
const service = new TTSService();
|
|
11
|
+
const first = await service._getClient();
|
|
12
|
+
expect(await service.isAvailable()).toBe(true);
|
|
13
|
+
token = 'second';
|
|
14
|
+
expect(await service._getClient()).not.toBe(first);
|
|
15
|
+
token = '';
|
|
16
|
+
expect(await service.isAvailable()).toBe(false);
|
|
17
|
+
expect(await service._getClient()).toBeNull();
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
it('TTS does not present credential read failure as unavailable success', async () => {
|
|
21
|
+
configureOpenCodeCredentials(() => ({ credential: { list: async () => { throw new Error('private'); } } }));
|
|
22
|
+
await expect(new TTSService().isAvailable()).rejects.toThrow(/^Unable to read OpenCode credentials$/);
|
|
23
|
+
});
|
package/server/lib/tts/routes.js
CHANGED
|
@@ -62,7 +62,7 @@ export function registerTtsRoutes(app, { sayTTSCapability }) {
|
|
|
62
62
|
const { ttsService } = await getTtsModule();
|
|
63
63
|
|
|
64
64
|
// Check availability - server-configured key, client-provided key, or custom server URL
|
|
65
|
-
const hasServerKey = ttsService.isAvailable();
|
|
65
|
+
const hasServerKey = await ttsService.isAvailable();
|
|
66
66
|
const hasClientKey = apiKey && typeof apiKey === 'string' && apiKey.trim().length > 0;
|
|
67
67
|
const hasCustomBaseURL = typeof normalizedBaseURL === 'string' && normalizedBaseURL.length > 0;
|
|
68
68
|
|
|
@@ -134,7 +134,7 @@ export function registerTtsRoutes(app, { sayTTSCapability }) {
|
|
|
134
134
|
try {
|
|
135
135
|
const { ttsService } = await getTtsModule();
|
|
136
136
|
res.json({
|
|
137
|
-
available: ttsService.isAvailable(),
|
|
137
|
+
available: await ttsService.isAvailable(),
|
|
138
138
|
voices: [
|
|
139
139
|
'alloy', 'ash', 'ballad', 'coral', 'echo', 'fable',
|
|
140
140
|
'nova', 'onyx', 'sage', 'shimmer', 'verse', 'marin', 'cedar'
|