@jini-ai/ui 0.4.4 → 0.4.6
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/CHANGELOG.md +10 -0
- package/README.md +20 -0
- package/dist/core/theme/default.d.ts +4 -0
- package/dist/core/theme/default.js +11 -0
- package/dist/core/theme/stylesheet.d.ts +8 -0
- package/dist/core/theme/stylesheet.js +14 -0
- package/dist/core/theme/types.d.ts +39 -0
- package/dist/core/theme/types.js +2 -0
- package/dist/core/theme/validation.d.ts +15 -0
- package/dist/core/theme/validation.js +55 -0
- package/dist/features/admin-widgets/components/Select/Select.d.ts +11 -3
- package/dist/features/admin-widgets/components/Select/Select.hooks.d.ts +8 -13
- package/dist/features/admin-widgets/components/Select/Select.hooks.js +37 -215
- package/dist/features/admin-widgets/components/Select/Select.js +4 -3
- package/dist/features/appearance/constants.js +1 -1
- package/dist/features/connectors/rules.d.ts +1 -6
- package/dist/features/connectors/types.d.ts +1 -6
- package/dist/features/connectors/types.js +1 -6
- package/dist/features/execution/http-adapter.d.ts +111 -0
- package/dist/features/execution/http-adapter.js +160 -0
- package/dist/features/execution/index.d.ts +4 -0
- package/dist/features/execution/index.js +2 -0
- package/dist/features/execution/react/hooks/use-execution-credential.hooks.d.ts +232 -0
- package/dist/features/execution/react/hooks/use-execution-credential.hooks.js +217 -0
- package/dist/features/html-editor/canvas-style.d.ts +1 -1
- package/dist/features/html-editor/canvas-style.js +2 -2
- package/dist/features/i18n/dictionaries/settings-dialog.ar.js +2 -0
- package/dist/features/i18n/dictionaries/settings-dialog.bn.js +2 -0
- package/dist/features/i18n/dictionaries/settings-dialog.de.js +2 -0
- package/dist/features/i18n/dictionaries/settings-dialog.en.js +2 -0
- package/dist/features/i18n/dictionaries/settings-dialog.es.js +2 -0
- package/dist/features/i18n/dictionaries/settings-dialog.fa.js +2 -0
- package/dist/features/i18n/dictionaries/settings-dialog.fr.js +2 -0
- package/dist/features/i18n/dictionaries/settings-dialog.hi.js +2 -0
- package/dist/features/i18n/dictionaries/settings-dialog.hu.js +2 -0
- package/dist/features/i18n/dictionaries/settings-dialog.id.js +2 -0
- package/dist/features/i18n/dictionaries/settings-dialog.it.js +2 -0
- package/dist/features/i18n/dictionaries/settings-dialog.ja.js +2 -0
- package/dist/features/i18n/dictionaries/settings-dialog.ko.js +2 -0
- package/dist/features/i18n/dictionaries/settings-dialog.pl.js +2 -0
- package/dist/features/i18n/dictionaries/settings-dialog.pt-BR.js +2 -0
- package/dist/features/i18n/dictionaries/settings-dialog.ru.js +2 -0
- package/dist/features/i18n/dictionaries/settings-dialog.th.js +2 -0
- package/dist/features/i18n/dictionaries/settings-dialog.tr.js +2 -0
- package/dist/features/i18n/dictionaries/settings-dialog.uk.js +2 -0
- package/dist/features/i18n/dictionaries/settings-dialog.ur.js +2 -0
- package/dist/features/i18n/dictionaries/settings-dialog.zh-CN.js +2 -0
- package/dist/features/i18n/dictionaries/settings-dialog.zh-TW.js +2 -0
- package/dist/features/i18n/dictionary-translator.d.ts +5 -2
- package/dist/features/i18n/dictionary-translator.js +1 -1
- package/dist/features/i18n/index.d.ts +1 -1
- package/dist/features/i18n/index.js +1 -1
- package/dist/features/i18n/template-i18n.d.ts +11 -0
- package/dist/features/i18n/template-i18n.js +12 -0
- package/dist/features/interactive-ui/providers/recharts/chart-tokens.d.ts +1 -1
- package/dist/features/interactive-ui/providers/recharts/chart-tokens.js +1 -1
- package/dist/features/lexical-rich-text-editor/deserialize.d.ts +0 -5
- package/dist/features/lexical-rich-text-editor/deserialize.js +0 -5
- package/dist/features/lexical-rich-text-editor/mention-node.d.ts +5 -17
- package/dist/features/lexical-rich-text-editor/mention-node.js +5 -17
- package/dist/features/lexical-rich-text-editor/mention-parser.d.ts +2 -4
- package/dist/features/lexical-rich-text-editor/serialize.d.ts +0 -4
- package/dist/features/lexical-rich-text-editor/serialize.js +0 -4
- package/dist/features/lexical-rich-text-editor/types.d.ts +3 -13
- package/dist/features/lexical-rich-text-editor/types.js +3 -13
- package/dist/features/mcp-ui/early-message-buffer.d.ts +1 -2
- package/dist/features/mcp-ui/early-message-buffer.js +1 -2
- package/dist/features/mcp-ui/escape.d.ts +4 -0
- package/dist/features/mcp-ui/escape.js +2 -9
- package/dist/features/mcp-ui/index.d.ts +1 -0
- package/dist/features/mcp-ui/index.js +1 -0
- package/dist/features/mcp-ui/protocol.d.ts +2 -5
- package/dist/features/mcp-ui/protocol.js +2 -5
- package/dist/features/mcp-ui/resource.d.ts +47 -7
- package/dist/features/mcp-ui/resource.js +5 -8
- package/dist/features/mcp-ui/secret-card.d.ts +115 -0
- package/dist/features/mcp-ui/secret-card.js +137 -0
- package/dist/features/mcp-ui/surfaces/confirmation.js +5 -0
- package/dist/features/mcp-ui/surfaces/fields.d.ts +5 -2
- package/dist/features/mcp-ui/surfaces/fields.js +3 -1
- package/dist/features/mcp-ui/surfaces/form.d.ts +4 -1
- package/dist/features/mcp-ui/surfaces/form.js +65 -3
- package/dist/features/mcp-ui/surfaces/outcome.d.ts +2 -0
- package/dist/features/mcp-ui/surfaces/text-input.d.ts +14 -6
- package/dist/features/mcp-ui/surfaces/text-input.js +16 -2
- package/dist/features/mcp-ui/surfaces/tokens.js +1 -1
- package/dist/features/memory/react/components/MemoryConnectedPanel.d.ts +1 -1
- package/dist/features/memory/react/components/MemoryConnectedPanel.js +0 -1
- package/dist/features/panel-kit/helpers/format-timestamp.d.ts +5 -3
- package/dist/features/panel-kit/helpers/format-timestamp.js +13 -4
- package/dist/features/panel-kit/helpers/permissions.js +2 -0
- package/dist/features/panel-kit/helpers/resolve-active-tab-id.js +2 -0
- package/dist/features/panel-kit/helpers/standing-draft-local-backup.d.ts +51 -0
- package/dist/features/panel-kit/helpers/standing-draft-local-backup.js +77 -0
- package/dist/features/panel-kit/helpers/standing-draft-recovery-message.d.ts +8 -0
- package/dist/features/panel-kit/helpers/standing-draft-recovery-message.js +10 -0
- package/dist/features/panel-kit/hooks/use-async-action.hooks.d.ts +1 -0
- package/dist/features/panel-kit/hooks/use-dirty-guard.hooks.d.ts +1 -0
- package/dist/features/panel-kit/hooks/use-dirty-guard.hooks.js +1 -0
- package/dist/features/panel-kit/hooks/use-serial-writes.hooks.d.ts +8 -0
- package/dist/features/panel-kit/hooks/use-serial-writes.hooks.js +3 -0
- package/dist/features/panel-kit/hooks/use-settings-slice.hooks.d.ts +211 -0
- package/dist/features/panel-kit/hooks/use-settings-slice.hooks.js +382 -0
- package/dist/features/panel-kit/hooks/use-standing-draft-autosave.hooks.d.ts +179 -0
- package/dist/features/panel-kit/hooks/use-standing-draft-autosave.hooks.js +300 -0
- package/dist/features/panel-kit/index.d.ts +7 -1
- package/dist/features/panel-kit/index.js +5 -1
- package/dist/features/resource-dashboard/types.d.ts +8 -41
- package/dist/features/resource-dashboard/types.js +3 -26
- package/dist/features/tab-strip/constants.d.ts +3 -4
- package/dist/features/tab-strip/constants.js +3 -4
- package/dist/features/tab-strip/dependencies.d.ts +1 -6
- package/dist/features/tab-strip/dependencies.js +1 -6
- package/dist/features/tab-strip/react/components/TabBar.d.ts +33 -1
- package/dist/features/tab-strip/react/components/TabBar.hooks.d.ts +57 -5
- package/dist/features/tab-strip/react/components/TabBar.hooks.js +63 -5
- package/dist/features/tab-strip/react/components/TabBar.js +12 -1
- package/dist/features/tab-strip/rules.d.ts +4 -17
- package/dist/features/tab-strip/rules.js +4 -17
- package/dist/features/tab-strip/types.d.ts +2 -13
- package/dist/icon-name.d.ts +1 -1
- package/dist/icon-name.js +1 -0
- package/dist/index.d.ts +42 -417
- package/dist/index.js +42 -430
- package/dist/react/components/CustomSelect.d.ts +6 -2
- package/dist/react/components/CustomSelect.js +20 -91
- package/dist/react/components/Icon.d.ts +6 -0
- package/dist/react/components/Icon.js +12 -0
- package/dist/react/components/select-popover.d.ts +106 -0
- package/dist/react/components/select-popover.js +210 -0
- package/dist/renderers/sandbox-scripts.d.ts +29 -0
- package/dist/renderers/sandbox-scripts.js +127 -0
- package/dist/renderers/sandboxed-document.js +3 -86
- package/dist/renderers/srcdoc/build.d.ts +10 -17
- package/dist/renderers/srcdoc/build.js +31 -234
- package/dist/styles/variables.css +2 -2
- package/dist/theme/apply.d.ts +1 -1
- package/dist/theme/apply.js +2 -2
- package/dist/theme/color-scheme.d.ts +1 -1
- package/dist/theme/default.d.ts +1 -3
- package/dist/theme/default.js +2 -10
- package/dist/theme/index.d.ts +3 -3
- package/dist/theme/index.js +2 -2
- package/dist/theme/stylesheet.d.ts +1 -7
- package/dist/theme/stylesheet.js +2 -13
- package/dist/theme/types.d.ts +1 -38
- package/dist/theme/types.js +2 -1
- package/dist/theme/validation.d.ts +1 -14
- package/dist/theme/validation.js +2 -54
- package/dist/utils/endpoint-policy.d.ts +21 -39
- package/dist/utils/endpoint-policy.js +19 -107
- package/dist/utils/file-system-errors.d.ts +1 -2
- package/dist/utils/file-system-errors.js +1 -2
- package/dist/utils/ime-composing.d.ts +0 -3
- package/dist/utils/ime-composing.js +0 -3
- package/dist/utils/platform.d.ts +1 -4
- package/dist/utils/platform.js +1 -4
- package/dist/utils/smooth-scroll-to-top.d.ts +1 -3
- package/dist/utils/smooth-scroll-to-top.js +3 -13
- package/dist/utils/timezone.js +1 -5
- package/dist/utils/uuid.d.ts +0 -3
- package/dist/utils/uuid.js +0 -3
- package/dist/utils/visual-stability.d.ts +1 -3
- package/dist/utils/visual-stability.js +1 -3
- package/package.json +74 -15
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { defaultAdminTheme } from '../../theme/default.js';
|
|
1
|
+
import { defaultAdminTheme } from '../../core/theme/default.js';
|
|
2
2
|
/** Plain-English defaults for the three segmented-control options — a host
|
|
3
3
|
* wanting localized copy overrides via `AppearanceTabProps.themeLabels` (the
|
|
4
4
|
* component wraps each through `useT()` regardless, so this is only the
|
|
@@ -1,9 +1,4 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Pure logic ported from OD's ConnectorsBrowser.tsx + the two small pure
|
|
3
|
-
* helpers it imported from EntryView.tsx (isTrustedConnectorCallbackOrigin,
|
|
4
|
-
* sortConnectorsForSearch/getConnectorSearchScore/sortConnectorsForDisplay).
|
|
5
|
-
* No React, no transport, no DOM — see `packages/ui/archived provenance ledger`.
|
|
6
|
-
*/
|
|
1
|
+
/** Connector callback-origin validation and search/display sorting; no React, transport or DOM. */
|
|
7
2
|
import type { Connector, ConnectorAuthorizationPendingState, ConnectorActionResult, ConnectorStatusMap, ConnectorPanelAlert, ConnectorStatus, ProviderTab } from './types.js';
|
|
8
3
|
/** Restricts the catalog to a single provider tab's `match` predicate, defaulting to the first tab. */
|
|
9
4
|
export declare function scopeConnectorsToProvider(connectors: Connector[], providerTabs: readonly ProviderTab[], selectedProviderId: string): Connector[];
|
|
@@ -1,9 +1,4 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Generic OAuth-integration-marketplace domain types. Ported from the origin
|
|
3
|
-
* product's ConnectorsBrowser.tsx (see the vendored reference tree cited in
|
|
4
|
-
* `packages/ui/archived provenance ledger`), stripped of Connector Provider/product-specific
|
|
5
|
-
* wire-shape specifics — see that file for the full provenance note.
|
|
6
|
-
*/
|
|
1
|
+
/** Generic OAuth-integration marketplace types; hosts own provider-specific wire shapes. */
|
|
7
2
|
export type ConnectorStatus = 'available' | 'connected' | 'error' | 'disabled';
|
|
8
3
|
export interface ConnectorToolSafety {
|
|
9
4
|
sideEffect: string;
|
|
@@ -1,8 +1,3 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Generic OAuth-integration-marketplace domain types. Ported from the origin
|
|
3
|
-
* product's ConnectorsBrowser.tsx (see the vendored reference tree cited in
|
|
4
|
-
* `packages/ui/archived provenance ledger`), stripped of Connector Provider/product-specific
|
|
5
|
-
* wire-shape specifics — see that file for the full provenance note.
|
|
6
|
-
*/
|
|
1
|
+
/** Generic OAuth-integration marketplace types; hosts own provider-specific wire shapes. */
|
|
7
2
|
export {};
|
|
8
3
|
//# sourceMappingURL=types.js.map
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
import type { ByokConfig, DetectedAgent } from './types.js';
|
|
2
|
+
import type { ExecutionPort } from './ports.js';
|
|
3
|
+
/** Decoded HTTP operations; hosts retain auth, URLs and transport validation. */
|
|
4
|
+
export interface ExecutionProbeTransport {
|
|
5
|
+
detectExecutionAgents(required: Record<string, never>, optional?: Record<string, never>): Promise<{
|
|
6
|
+
data: readonly DetectedAgent[];
|
|
7
|
+
}>;
|
|
8
|
+
testExecutionConnection(required: Pick<ByokConfig, "protocol" | "baseUrl" | "apiKey" | "model"> & ExecutionProbeFlags, optional?: Record<string, never>): Promise<{
|
|
9
|
+
ok: boolean;
|
|
10
|
+
message?: string;
|
|
11
|
+
}>;
|
|
12
|
+
testExecutionAgent(required: {
|
|
13
|
+
agentId: string;
|
|
14
|
+
model?: string;
|
|
15
|
+
}, optional?: Record<string, never>): ReturnType<NonNullable<ExecutionPort["testAgent"]>>;
|
|
16
|
+
listExecutionModels(required: Pick<ByokConfig, "protocol" | "baseUrl" | "apiKey"> & ExecutionProbeFlags, optional?: Record<string, never>): Promise<{
|
|
17
|
+
ok: boolean;
|
|
18
|
+
models: readonly string[];
|
|
19
|
+
message?: string;
|
|
20
|
+
}>;
|
|
21
|
+
}
|
|
22
|
+
interface ExecutionProbeFlags {
|
|
23
|
+
useStoredCredential?: boolean;
|
|
24
|
+
useAdminStoredCredential?: boolean;
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Unwraps `{ data }` off an `api.*` response, or throws a named error identifying which call
|
|
28
|
+
* produced the malformed body.
|
|
29
|
+
*
|
|
30
|
+
* `request()` (`lib/api.ts`) validates shape on the FAILURE path only — a non-2xx always becomes
|
|
31
|
+
* an `ApiError` — but a 2xx body is returned as `body as T` with no runtime check that it actually
|
|
32
|
+
* has the shape `T` claims. Every caller in this file that read `.data` straight off that promise
|
|
33
|
+
* used to let a malformed 200 (body present, `data` missing/`null`) fall through to whatever the
|
|
34
|
+
* FIRST thing touching the result happened to be: `loadExecutionConfig`'s own `rows.map(...)`
|
|
35
|
+
* threw `Cannot read properties of undefined (reading 'map')`, while `loadAdminExecutionCredential`
|
|
36
|
+
* had no local dereference at all, so it silently RESOLVED to `undefined` and the crash only
|
|
37
|
+
* surfaced later, at whichever caller first read a field off it (live case: `AssistantDock.hooks
|
|
38
|
+
* .tsx`'s `view.isSet`, `Cannot read properties of undefined (reading 'isSet')`). Both are the same
|
|
39
|
+
* underlying defect wearing different symptoms depending on what the caller happened to do next.
|
|
40
|
+
* Centralizing the check here means every one of those call sites fails at its own source, with a
|
|
41
|
+
* message naming the actual `api.*` method — not a raw property-access trace an on-call engineer
|
|
42
|
+
* has to work backward from.
|
|
43
|
+
*
|
|
44
|
+
* Every existing caller's own failure contract is unchanged by this: `loadExecutionConfig`/
|
|
45
|
+
* `loadAdminExecutionCredential` still get caught by `AssistantDock`'s `.catch()`-and-log-and-
|
|
46
|
+
* fall-back-to-defaults; `saveAdminExecutionCredential` still surfaces as a failed save; the local
|
|
47
|
+
* agent-detection port's methods still reject, per this file's own error-reporting contract (see
|
|
48
|
+
* `requestAgentDetection`'s doc). This function only makes the THROWN message legible — it never
|
|
49
|
+
* suppresses a malformed response into a silent default (an earlier draft's `?? []` would have hidden
|
|
50
|
+
* the same failure behind "shows up as empty settings, no explanation" instead of fixing it).
|
|
51
|
+
*
|
|
52
|
+
* @complexity O(1) — one shape check, no iteration.
|
|
53
|
+
*/
|
|
54
|
+
export declare function requireExecutionResponseData<T>({ response, callSite }: {
|
|
55
|
+
response: {
|
|
56
|
+
data?: T | null;
|
|
57
|
+
} | null | undefined;
|
|
58
|
+
callSite: string;
|
|
59
|
+
}, _options?: Record<string, never>): T;
|
|
60
|
+
export interface CreateExecutionPortOptions {
|
|
61
|
+
/**
|
|
62
|
+
* When `true`, probes that receive no typed API key ask the server to use the workspace's STORED
|
|
63
|
+
* site credential instead.
|
|
64
|
+
*
|
|
65
|
+
* For the AI Assistant tab, whose key is encrypted server-side and write-only: the operator
|
|
66
|
+
* returns to a screen with a saved, working key and an EMPTY field, so "Test connection" and model
|
|
67
|
+
* discovery had nothing to send and sat permanently disabled next to a credential that works.
|
|
68
|
+
*
|
|
69
|
+
* Left `false` for Settings → Execution mode, and that default is the safety property, not an
|
|
70
|
+
* oversight: that screen probes the ADMIN's own credential (server-side since 2026-08-05, but
|
|
71
|
+
* still a DIFFERENT stored row from the site's), a different purpose from this flag entirely. If
|
|
72
|
+
* it opted in, an empty field there would silently probe — and report results for — the visitor
|
|
73
|
+
* key, crossing the boundary ADR-058 §5 makes structural. (The admin's OWN stored fallback for a
|
|
74
|
+
* BYOK *turn* is unconditional and happens entirely server-side in `assistant-byok.ts` — this
|
|
75
|
+
* flag only ever concerns the SITE credential these `test-connection`/`list-models` probes can
|
|
76
|
+
* optionally fall back to.)
|
|
77
|
+
*/
|
|
78
|
+
useStoredCredential?: boolean;
|
|
79
|
+
/**
|
|
80
|
+
* When `true`, probes that receive no typed API key ask the server to use the CALLING ADMIN'S OWN
|
|
81
|
+
* stored execution credential — the other write-only key, and a different row from
|
|
82
|
+
* {@link CreateExecutionPortOptions.useStoredCredential}'s.
|
|
83
|
+
*
|
|
84
|
+
* Same symptom on the other screen. The admin's own key moved server-side on 2026-08-05, so
|
|
85
|
+
* `ExecutionTab`'s Model field asked the server to discover models with an empty key and got back
|
|
86
|
+
* "No API key — model discovery needs the key from this browser" — beside a stored key the server
|
|
87
|
+
* uses on every admin turn. The visible consequence was that the admin BYOK panel never reached
|
|
88
|
+
* `modelDiscovery.status === 'ok'`, so `ByokProviderForm` fell back to its free-text Model input
|
|
89
|
+
* while the visitor panel next to it showed a real live-model picker from the same component.
|
|
90
|
+
*
|
|
91
|
+
* The server scopes the row to the SESSION's principal, so this can only ever reach the caller's
|
|
92
|
+
* own credential. Deliberately a second flag rather than a widened `useStoredCredential`: see
|
|
93
|
+
* `server/inbound/admin-http/routes/assistant/stored-credential-probe.ts`'s header.
|
|
94
|
+
*/
|
|
95
|
+
useAdminStoredCredential?: boolean;
|
|
96
|
+
}
|
|
97
|
+
/** Create one adapter per host transport. Keep it outside React mounts so detection is shared.
|
|
98
|
+
* Failed detections remain retryable; rescans always replace the cache with a fresh request.
|
|
99
|
+
* Transport errors reject; connection/CLI outcomes may resolve ok:false.
|
|
100
|
+
* @complexity O(1) local work per operation, excluding transport.
|
|
101
|
+
* @example const adapter = createExecutionHttpAdapter({ transport }, {});
|
|
102
|
+
* const port = adapter.createPort({}, { useAdminStoredCredential: true });
|
|
103
|
+
*/
|
|
104
|
+
export declare function createExecutionHttpAdapter({ transport }: {
|
|
105
|
+
transport: ExecutionProbeTransport;
|
|
106
|
+
}, _options?: Record<string, never>): {
|
|
107
|
+
createPort: (_required?: Record<string, never>, options?: CreateExecutionPortOptions) => ExecutionPort;
|
|
108
|
+
resetDetectionCache: (_required?: Record<string, never>, _optional?: Record<string, never>) => void;
|
|
109
|
+
};
|
|
110
|
+
export {};
|
|
111
|
+
//# sourceMappingURL=http-adapter.d.ts.map
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Unwraps `{ data }` off an `api.*` response, or throws a named error identifying which call
|
|
3
|
+
* produced the malformed body.
|
|
4
|
+
*
|
|
5
|
+
* `request()` (`lib/api.ts`) validates shape on the FAILURE path only — a non-2xx always becomes
|
|
6
|
+
* an `ApiError` — but a 2xx body is returned as `body as T` with no runtime check that it actually
|
|
7
|
+
* has the shape `T` claims. Every caller in this file that read `.data` straight off that promise
|
|
8
|
+
* used to let a malformed 200 (body present, `data` missing/`null`) fall through to whatever the
|
|
9
|
+
* FIRST thing touching the result happened to be: `loadExecutionConfig`'s own `rows.map(...)`
|
|
10
|
+
* threw `Cannot read properties of undefined (reading 'map')`, while `loadAdminExecutionCredential`
|
|
11
|
+
* had no local dereference at all, so it silently RESOLVED to `undefined` and the crash only
|
|
12
|
+
* surfaced later, at whichever caller first read a field off it (live case: `AssistantDock.hooks
|
|
13
|
+
* .tsx`'s `view.isSet`, `Cannot read properties of undefined (reading 'isSet')`). Both are the same
|
|
14
|
+
* underlying defect wearing different symptoms depending on what the caller happened to do next.
|
|
15
|
+
* Centralizing the check here means every one of those call sites fails at its own source, with a
|
|
16
|
+
* message naming the actual `api.*` method — not a raw property-access trace an on-call engineer
|
|
17
|
+
* has to work backward from.
|
|
18
|
+
*
|
|
19
|
+
* Every existing caller's own failure contract is unchanged by this: `loadExecutionConfig`/
|
|
20
|
+
* `loadAdminExecutionCredential` still get caught by `AssistantDock`'s `.catch()`-and-log-and-
|
|
21
|
+
* fall-back-to-defaults; `saveAdminExecutionCredential` still surfaces as a failed save; the local
|
|
22
|
+
* agent-detection port's methods still reject, per this file's own error-reporting contract (see
|
|
23
|
+
* `requestAgentDetection`'s doc). This function only makes the THROWN message legible — it never
|
|
24
|
+
* suppresses a malformed response into a silent default (an earlier draft's `?? []` would have hidden
|
|
25
|
+
* the same failure behind "shows up as empty settings, no explanation" instead of fixing it).
|
|
26
|
+
*
|
|
27
|
+
* @complexity O(1) — one shape check, no iteration.
|
|
28
|
+
*/
|
|
29
|
+
export function requireExecutionResponseData({ response, callSite }, _options = {}) {
|
|
30
|
+
if (response === null || response === undefined || response.data === undefined || response.data === null) {
|
|
31
|
+
throw new Error(`${callSite} response missing data`);
|
|
32
|
+
}
|
|
33
|
+
return response.data;
|
|
34
|
+
}
|
|
35
|
+
/** Create one adapter per host transport. Keep it outside React mounts so detection is shared.
|
|
36
|
+
* Failed detections remain retryable; rescans always replace the cache with a fresh request.
|
|
37
|
+
* Transport errors reject; connection/CLI outcomes may resolve ok:false.
|
|
38
|
+
* @complexity O(1) local work per operation, excluding transport.
|
|
39
|
+
* @example const adapter = createExecutionHttpAdapter({ transport }, {});
|
|
40
|
+
* const port = adapter.createPort({}, { useAdminStoredCredential: true });
|
|
41
|
+
*/
|
|
42
|
+
export function createExecutionHttpAdapter({ transport }, _options = {}) {
|
|
43
|
+
/**
|
|
44
|
+
* host's real `ExecutionPort` — wired to `src/server/modules/assistant-
|
|
45
|
+
* execution.ts`'s 3 routes (`@jini-ai/agent-runtime` underneath). Follows
|
|
46
|
+
* `@jini-ai/ui`'s `ports.ts` contract (error-reporting contract §3.1):
|
|
47
|
+
*
|
|
48
|
+
* - `detectLocalAgents`/`rescanLocalAgents`/`listModels` REJECT on any
|
|
49
|
+
* failure (a network error, a non-2xx from this admin's own route, a
|
|
50
|
+
* provider that could not be reached). An empty array is reserved for a
|
|
51
|
+
* real "detection ran, found nothing" / "no models" outcome — collapsing a
|
|
52
|
+
* broken request into `[]` would make it indistinguishable from that,
|
|
53
|
+
* which is exactly the bug this port used to have.
|
|
54
|
+
* - `testConnection` is the one documented exception: `api.testExecutionConnection`
|
|
55
|
+
* already classifies "reached the provider, credentials rejected" as an
|
|
56
|
+
* `{ok:false}` VALUE server-side (`test-connection.ts`), so this method
|
|
57
|
+
* simply returns whatever it resolves with. It only REJECTS when the
|
|
58
|
+
* route call itself fails (session expired, network down, this admin's own
|
|
59
|
+
* route 500ing) — a case with no provider-side answer to report as a value.
|
|
60
|
+
*/
|
|
61
|
+
async function requestAgentDetection() {
|
|
62
|
+
return requireExecutionResponseData({ response: await transport.detectExecutionAgents({}, {}), callSite: "detectExecutionAgents" }, {});
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Last detection, shared across every `ExecutionTab` mount in this tab's
|
|
66
|
+
* lifetime.
|
|
67
|
+
*
|
|
68
|
+
* Detection is not cheap and it is not local: `detect-agents.ts` calls
|
|
69
|
+
* `@jini-ai/agent-runtime`'s `detectAgents()`, which SPAWNS every known CLI
|
|
70
|
+
* with `--version` — two dozen processes on the host server per call. The tab
|
|
71
|
+
* asks for it unconditionally on mount (`useExecutionTab.ts`'s auto-detect
|
|
72
|
+
* effect), and the shell renders only the ACTIVE tab's panel, so leaving
|
|
73
|
+
* Execution mode unmounts the component and returning to it re-runs the whole
|
|
74
|
+
* sweep. Same on every visit to the settings page. Without this cache the
|
|
75
|
+
* operator pays a fresh 24-process scan for what is, in practice, a fixed
|
|
76
|
+
* answer.
|
|
77
|
+
*
|
|
78
|
+
* Host-scoped rather than a `useRef`/context on purpose: the point is to
|
|
79
|
+
* outlive the component, and `SettingsUi` builds a NEW port object per mount
|
|
80
|
+
* (`useRef(createExecutionPort())`), so anything held on the port instance
|
|
81
|
+
* would die with it.
|
|
82
|
+
*
|
|
83
|
+
* Deliberately no TTL. Installing a CLI mid-session is the only way this goes
|
|
84
|
+
* stale, and that case already has a purpose-built, clearly-labelled escape
|
|
85
|
+
* hatch — the Rescan button, which bypasses this cache (see
|
|
86
|
+
* `rescanLocalAgents` below). A reload clears it too. A TTL would only add a
|
|
87
|
+
* second, invisible refresh rule on top of the explicit one.
|
|
88
|
+
*/
|
|
89
|
+
let cachedDetection = null;
|
|
90
|
+
/** Caches the in-flight promise, not just the settled value — two tabs
|
|
91
|
+
* mounting in the same frame then share one request instead of racing two
|
|
92
|
+
* identical process sweeps. */
|
|
93
|
+
function cacheDetection(inFlight) {
|
|
94
|
+
cachedDetection = inFlight;
|
|
95
|
+
// A rejection must never be cached: one network blip would otherwise pin the
|
|
96
|
+
// error in place for the rest of the session, and the port's contract is
|
|
97
|
+
// that a failure REJECTS (see this function's doc) — so a retry has to be
|
|
98
|
+
// able to actually retry.
|
|
99
|
+
inFlight.catch(() => {
|
|
100
|
+
if (cachedDetection === inFlight)
|
|
101
|
+
cachedDetection = null;
|
|
102
|
+
});
|
|
103
|
+
return inFlight;
|
|
104
|
+
}
|
|
105
|
+
/** Test seam. Module state persists across cases in a file, so a suite that
|
|
106
|
+
* asserts on detection has to be able to start from cold. */
|
|
107
|
+
function resetLocalAgentDetectionCache(_required = {}, _optional = {}) {
|
|
108
|
+
cachedDetection = null;
|
|
109
|
+
}
|
|
110
|
+
function createExecutionPort(_required = {}, options = {}) {
|
|
111
|
+
const { useStoredCredential = false, useAdminStoredCredential = false } = options;
|
|
112
|
+
return {
|
|
113
|
+
async detectLocalAgents() {
|
|
114
|
+
return cachedDetection ?? cacheDetection(requestAgentDetection());
|
|
115
|
+
},
|
|
116
|
+
async rescanLocalAgents() {
|
|
117
|
+
// Explicit operator action: always re-probe, and make the fresh result
|
|
118
|
+
// the new baseline for subsequent mounts.
|
|
119
|
+
return cacheDetection(requestAgentDetection());
|
|
120
|
+
},
|
|
121
|
+
async testConnection(config) {
|
|
122
|
+
return transport.testExecutionConnection({
|
|
123
|
+
protocol: config.protocol,
|
|
124
|
+
baseUrl: config.baseUrl,
|
|
125
|
+
apiKey: config.apiKey,
|
|
126
|
+
model: config.model,
|
|
127
|
+
...(useStoredCredential ? { useStoredCredential: true } : {}),
|
|
128
|
+
...(useAdminStoredCredential ? { useAdminStoredCredential: true } : {}),
|
|
129
|
+
}, {});
|
|
130
|
+
},
|
|
131
|
+
async testAgent(agentId, model) {
|
|
132
|
+
// Same `{ok:false}`-is-a-value split as `testConnection`: the route
|
|
133
|
+
// classifies "the CLI ran and reported it is not usable" server-side and
|
|
134
|
+
// returns it as a value, and REJECTS (throws out of `api.*`) only when
|
|
135
|
+
// the probe could not run at all.
|
|
136
|
+
return transport.testExecutionAgent({ agentId, ...(model ? { model } : {}) }, {});
|
|
137
|
+
},
|
|
138
|
+
async listModels(config) {
|
|
139
|
+
const result = await transport.listExecutionModels({
|
|
140
|
+
protocol: config.protocol,
|
|
141
|
+
baseUrl: config.baseUrl,
|
|
142
|
+
apiKey: config.apiKey,
|
|
143
|
+
...(useStoredCredential ? { useStoredCredential: true } : {}),
|
|
144
|
+
...(useAdminStoredCredential ? { useAdminStoredCredential: true } : {}),
|
|
145
|
+
}, {});
|
|
146
|
+
// `result.ok === false` here is ALWAYS "discovery could not reach/read the
|
|
147
|
+
// provider" (auth failure, timeout, blocked base URL, unsupported protocol)
|
|
148
|
+
// — `list-models.ts`'s route never reports a reachable-but-genuinely-empty
|
|
149
|
+
// catalog as `ok:false`, so there is no legitimate value to return here.
|
|
150
|
+
// Reject with the server's own message rather than resolving `[]`.
|
|
151
|
+
if (!result.ok) {
|
|
152
|
+
throw new Error(result.message?.trim() ? result.message : "Model discovery failed");
|
|
153
|
+
}
|
|
154
|
+
return result.models;
|
|
155
|
+
},
|
|
156
|
+
};
|
|
157
|
+
}
|
|
158
|
+
return { createPort: createExecutionPort, resetDetectionCache: resetLocalAgentDetectionCache };
|
|
159
|
+
}
|
|
160
|
+
//# sourceMappingURL=http-adapter.js.map
|
|
@@ -21,4 +21,8 @@ export { SearchableModelSelect } from './react/components/SearchableModelSelect.
|
|
|
21
21
|
export type { SearchableModelSelectProps, SearchableModelSelectAdditionalOption, } from './react/components/SearchableModelSelect.js';
|
|
22
22
|
export { useExecutionTab } from './react/hooks/useExecutionTab.js';
|
|
23
23
|
export type { UseExecutionTabOptions, UseExecutionTabResult } from './react/hooks/useExecutionTab.js';
|
|
24
|
+
export { createExecutionHttpAdapter, requireExecutionResponseData } from './http-adapter.js';
|
|
25
|
+
export type { CreateExecutionPortOptions, ExecutionProbeTransport } from './http-adapter.js';
|
|
26
|
+
export { useExecutionCredential } from './react/hooks/use-execution-credential.hooks.js';
|
|
27
|
+
export type { UseExecutionCredentialInput, ExecutionCredentialController, ExecutionCredentialSaveState, ExecutionCredentialView, ExecutionCredentialPatch, ExecutionCredentialPort, ExecutionCredentialEffects } from './react/hooks/use-execution-credential.hooks.js';
|
|
24
28
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -10,4 +10,6 @@ export { AgentDiagnosticRow } from './react/components/AgentDiagnosticRow.js';
|
|
|
10
10
|
export { AgentCliEnvFields } from './react/components/AgentCliEnvFields.js';
|
|
11
11
|
export { SearchableModelSelect } from './react/components/SearchableModelSelect.js';
|
|
12
12
|
export { useExecutionTab } from './react/hooks/useExecutionTab.js';
|
|
13
|
+
export { createExecutionHttpAdapter, requireExecutionResponseData } from './http-adapter.js';
|
|
14
|
+
export { useExecutionCredential } from './react/hooks/use-execution-credential.hooks.js';
|
|
13
15
|
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1,232 @@
|
|
|
1
|
+
import type { ByokConfig } from "../../types.js";
|
|
2
|
+
import type { SettingsSliceRefreshPort } from "../../../panel-kit/hooks/use-settings-slice.hooks.js";
|
|
3
|
+
/** The metadata read by this controller. Hosts retain any additional write-only view fields. */
|
|
4
|
+
export interface ExecutionCredentialView {
|
|
5
|
+
isSet: boolean;
|
|
6
|
+
masked: string | null;
|
|
7
|
+
baseUrl: string | null;
|
|
8
|
+
}
|
|
9
|
+
/** Explicit key and non-secret patches remain distinct; the controller never autosaves a key. */
|
|
10
|
+
export interface ExecutionCredentialPatch {
|
|
11
|
+
apiKey?: string;
|
|
12
|
+
protocol?: string;
|
|
13
|
+
providerId?: string | null;
|
|
14
|
+
baseUrl?: string;
|
|
15
|
+
model?: string;
|
|
16
|
+
maxTokens?: number;
|
|
17
|
+
}
|
|
18
|
+
/** The host owns routes and authorization; only a write-only view may be returned. */
|
|
19
|
+
export interface ExecutionCredentialPort<TStored extends ExecutionCredentialView> {
|
|
20
|
+
loadAdminExecutionCredential(required: Record<string, never>, optional?: Record<string, never>): Promise<TStored>;
|
|
21
|
+
saveAdminExecutionCredential(required: {
|
|
22
|
+
patch: ExecutionCredentialPatch;
|
|
23
|
+
}, optional?: Record<string, never>): Promise<TStored>;
|
|
24
|
+
}
|
|
25
|
+
/** Host migration, notification and display policies. Product-specific error/copy stays in the host. */
|
|
26
|
+
export interface ExecutionCredentialEffects<TStored extends ExecutionCredentialView> {
|
|
27
|
+
namespace: string;
|
|
28
|
+
refreshPort: SettingsSliceRefreshPort;
|
|
29
|
+
readLegacyCredential(required: Record<string, never>, optional?: Record<string, never>): string | null;
|
|
30
|
+
clearLegacyCredential(required: Record<string, never>, optional?: Record<string, never>): void;
|
|
31
|
+
describeError(required: {
|
|
32
|
+
error: unknown;
|
|
33
|
+
fallback: string;
|
|
34
|
+
}, optional?: Record<string, never>): string;
|
|
35
|
+
hasTypedKey(required: {
|
|
36
|
+
apiKey: string;
|
|
37
|
+
}, optional?: Record<string, never>): boolean;
|
|
38
|
+
storedKeyIsForOtherEndpoint(required: {
|
|
39
|
+
stored: TStored | null;
|
|
40
|
+
baseUrl: string;
|
|
41
|
+
}, optional?: Record<string, never>): boolean;
|
|
42
|
+
hasUsableKey(required: {
|
|
43
|
+
apiKey: string;
|
|
44
|
+
stored: TStored | null;
|
|
45
|
+
baseUrl: string;
|
|
46
|
+
}, optional?: Record<string, never>): boolean;
|
|
47
|
+
storedKeyBlocksProbe(required: {
|
|
48
|
+
apiKey: string;
|
|
49
|
+
stored: TStored | null;
|
|
50
|
+
baseUrl: string;
|
|
51
|
+
}, optional?: Record<string, never>): boolean;
|
|
52
|
+
credentialHint(required: {
|
|
53
|
+
stored: TStored | null;
|
|
54
|
+
storedKeyIsForOtherEndpoint: boolean;
|
|
55
|
+
}, optional?: {
|
|
56
|
+
locale?: string | undefined;
|
|
57
|
+
}): string | undefined;
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* @file State for the admin's own BYOK credential — the "Save key" and "Save settings" controls and
|
|
61
|
+
* the one-time `localStorage`-to-server migration prompt, shared verbatim by `SettingsUi.tsx`'s Execution tab
|
|
62
|
+
* and `AiAssistant.tsx`'s `AdminExecutionMode` (both render `ExecutionTab` over the SAME ledger
|
|
63
|
+
* namespace and now the same server-side credential row; per `AdminExecutionMode`'s own "must never
|
|
64
|
+
* disagree" comment about `useStoredCredential`, the two mounts cannot be allowed to diverge here
|
|
65
|
+
* either — hence one hook rather than two independent implementations).
|
|
66
|
+
*
|
|
67
|
+
* Modeled closely on the already-shipped, owner-reviewed
|
|
68
|
+
* `features/ai-assistant/hooks/use-visitor-credential-form.hooks.ts`: explicit save only, the field
|
|
69
|
+
* clears on success, a failed save never touches what is already stored/local. The two differ where
|
|
70
|
+
* the underlying screens differ — this one does not own the whole BYOK form (only the "Save key"
|
|
71
|
+
* affordance under the key field and the "Save settings" affordance at the foot of the card, since
|
|
72
|
+
* `ExecutionTab` owns the provider chips and the fields themselves via the existing `core.execution`
|
|
73
|
+
* ledger slice) and it additionally owns the migration prompt, which the visitor screen has no
|
|
74
|
+
* equivalent of (there was never a browser-local visitor key to migrate).
|
|
75
|
+
*
|
|
76
|
+
* ## One button per patch (owner ruling, 2026-09-02)
|
|
77
|
+
*
|
|
78
|
+
* There used to be ONE control here, writing the key and the row's non-secret companion fields
|
|
79
|
+
* together. That overload is what made the screen lie: pressing it with a blank field sent a patch
|
|
80
|
+
* with no `apiKey` at all and still answered "Saved to the server, encrypted." Patching the message
|
|
81
|
+
* left the cause in place, and disabling the button on a blank field broke the only way to save a
|
|
82
|
+
* model change. The split closes both: `saveKey` writes `{ apiKey }`, `saveSettings` writes the
|
|
83
|
+
* non-secret fields and never an `apiKey` property, and each carries its own save state so neither
|
|
84
|
+
* can report the other's work.
|
|
85
|
+
*
|
|
86
|
+
* ## Cross-mount staleness (disclosed residual, closed here)
|
|
87
|
+
*
|
|
88
|
+
* `stored`/`apiKeyStoredExternally` is each mounted instance's OWN copy of one server fact — every
|
|
89
|
+
* `SettingsUi.tsx` mount, every `AiAssistant.tsx` mount, AND `AssistantDock.hooks.tsx`'s own
|
|
90
|
+
* independent `loadAdminExecutionCredential()` call (`useExecutionConfig`'s `hasStoredAdminKey`) are
|
|
91
|
+
* three separate reads of the same row. A save/migrate in ONE of them updated only its own local
|
|
92
|
+
* `stored` state, so the other two kept showing whatever they last fetched — briefly, if the operator
|
|
93
|
+
* happened to remount that screen soon after, or indefinitely for the dock, which mounts once at the
|
|
94
|
+
* app shell and never remounts for the session (`AssistantDock.tsx`'s own header comment). Now wired
|
|
95
|
+
* to `settings-refresh-bus.ts`, the same seam `useSettingsSlice`'s `runSave` already uses for exactly
|
|
96
|
+
* this "same-tab sibling with no shared state" problem: every successful write publishes
|
|
97
|
+
* `EXECUTION_NAMESPACE`, and every mount (including the publisher's own, harmlessly — see
|
|
98
|
+
* `runSave`'s doc for why that redundant self-refresh is accepted rather than tracked around) re-reads
|
|
99
|
+
* on receiving it.
|
|
100
|
+
*
|
|
101
|
+
* `port` (the two functions that reach `api.getAdminExecutionCredential`/`api.setAdminExecutionCredential`
|
|
102
|
+
* under the hood) is injected — see `admin-execution-credential-port.hooks.ts` for exactly what is and
|
|
103
|
+
* is not in it, and why. `useWiredAdminExecutionCredential` below is the zero-argument pair
|
|
104
|
+
* `SettingsUi.tsx`/`AiAssistant.tsx` actually mount; `AssistantDock.hooks.tsx`'s own separate
|
|
105
|
+
* `loadAdminExecutionCredential()` call (this file's "Cross-mount staleness" doc above) is a
|
|
106
|
+
* different hook entirely, out of scope here.
|
|
107
|
+
*/
|
|
108
|
+
export type ExecutionCredentialSaveState = {
|
|
109
|
+
status: "idle";
|
|
110
|
+
} | {
|
|
111
|
+
status: "saving";
|
|
112
|
+
} | {
|
|
113
|
+
status: "saved";
|
|
114
|
+
} | {
|
|
115
|
+
status: "error";
|
|
116
|
+
message: string;
|
|
117
|
+
};
|
|
118
|
+
export interface UseExecutionCredentialInput {
|
|
119
|
+
locale?: string | undefined;
|
|
120
|
+
/** The live BYOK half of whichever `ExecutionConfig` the host owns. Read fresh on every
|
|
121
|
+
* `saveKey()`/`migrateLegacyKey()` call (never captured once), so a save always sends whatever
|
|
122
|
+
* protocol/baseUrl/model/maxTokens the operator currently has selected. */
|
|
123
|
+
byok: ByokConfig;
|
|
124
|
+
/** How a successful key save clears the just-saved plaintext back out of the host's own state —
|
|
125
|
+
* same reasoning `use-visitor-credential-form.hooks.ts`'s `saveCredential` documents: leaving it
|
|
126
|
+
* in React state after it is safely persisted keeps it readable in devtools for no benefit, and
|
|
127
|
+
* the masked placeholder now carries the "which key" answer the field would otherwise be giving. */
|
|
128
|
+
onByokChange: (next: ByokConfig) => void;
|
|
129
|
+
}
|
|
130
|
+
export interface ExecutionCredentialController<TStored extends ExecutionCredentialView> {
|
|
131
|
+
/** The server's write-only view of what is currently stored — `null` until the initial GET
|
|
132
|
+
* settles. Never contains key material. */
|
|
133
|
+
stored: TStored | null;
|
|
134
|
+
/** Tells `ExecutionTab`'s `apiKeyStoredExternally` prop that an empty key field is not a missing
|
|
135
|
+
* required field, because a credential already exists server-side FOR THE FORM'S ENDPOINT. A key
|
|
136
|
+
* stored for another endpoint does not count (the server will not send it here), so after a
|
|
137
|
+
* provider switch the field is required again and Test connection waits for a typed key. Same rule
|
|
138
|
+
* as the visitor screen: `lib/stored-credential-endpoint.ts`'s `hasUsableKey`. */
|
|
139
|
+
apiKeyStoredExternally: boolean;
|
|
140
|
+
/** The server's `••••<last 4>`, or `undefined` when nothing is stored or the stored key belongs to
|
|
141
|
+
* another endpoint (under a different provider it would read as that provider's key) — feeds
|
|
142
|
+
* `ExecutionTab`'s `apiKeyPlaceholder`. Always a placeholder, never a value: `byok.apiKey` is
|
|
143
|
+
* untouched by this controller until an explicit save succeeds. */
|
|
144
|
+
apiKeyPlaceholder: string | undefined;
|
|
145
|
+
/** A key is stored, but the server saved it for another endpoint than the form's (a provider
|
|
146
|
+
* switch). Drives the key footer's "paste a key for this one" line. See
|
|
147
|
+
* `lib/stored-credential-endpoint.ts`'s `storedKeyIsForOtherEndpoint`; `false` until the stored
|
|
148
|
+
* view loads. */
|
|
149
|
+
storedKeyIsForOtherEndpoint: boolean;
|
|
150
|
+
/** Feeds `ExecutionTab`'s `canDiscoverModels`: `false` only when nothing is typed and the stored key
|
|
151
|
+
* belongs to another endpoint, the one discovery the server can only refuse. `ExecutionTab` reads
|
|
152
|
+
* it but never probes merely because it turned `true` — "Save settings" re-pointing the stored row
|
|
153
|
+
* flips it, and that must not send the old provider's key to the new endpoint unasked. */
|
|
154
|
+
canDiscoverModels: boolean;
|
|
155
|
+
/** The "Save key" button's own save state — and the migration prompt's, which writes the same
|
|
156
|
+
* key. Never advanced by {@link saveSettings}; see {@link settingsSaveState}. */
|
|
157
|
+
saveState: ExecutionCredentialSaveState;
|
|
158
|
+
/** The "Save settings" button's own save state, deliberately separate from {@link saveState}.
|
|
159
|
+
*
|
|
160
|
+
* One shared state is what made the old single control dishonest: its status line said "Saved to
|
|
161
|
+
* the server, encrypted." after a press that sent no key at all. Two buttons that write two
|
|
162
|
+
* disjoint patches need two answers, so each owns its own status line and neither can report the
|
|
163
|
+
* other's work. */
|
|
164
|
+
settingsSaveState: ExecutionCredentialSaveState;
|
|
165
|
+
/** Whether {@link saveKey} would do anything right now — the same "is there a key in the field"
|
|
166
|
+
* guard `saveKey` itself applies, exposed so the Save key button can disable correctly without
|
|
167
|
+
* duplicating the rule.
|
|
168
|
+
*
|
|
169
|
+
* Derived synchronously from the trimmed field on every render, never debounced: a delayed
|
|
170
|
+
* evaluation would leave the button enabled for the moment right after the field is cleared,
|
|
171
|
+
* which is the bug this rule exists to close, merely shortened. */
|
|
172
|
+
canSaveKey: boolean;
|
|
173
|
+
/**
|
|
174
|
+
* Explicit save, called ONLY from a "Save key" button press — never from any debounced or
|
|
175
|
+
* automatic path. Writes the KEY and nothing else: the patch is `{ apiKey }`, with no
|
|
176
|
+
* protocol/providerId/baseUrl/model/maxTokens riding along.
|
|
177
|
+
*
|
|
178
|
+
* That narrowing (owner ruling, 2026-09-02) is the fix for a whole defect class rather than one
|
|
179
|
+
* bug. A control that wrote two different things could not honestly report what it had just done,
|
|
180
|
+
* and the "which of my two jobs did I do" bookkeeping that answer needed was itself the thing that
|
|
181
|
+
* let an `apiKey: ""` reach the server. A button with one job needs no bookkeeping. The
|
|
182
|
+
* non-secret fields moved to {@link saveSettings}.
|
|
183
|
+
*
|
|
184
|
+
* A no-op when the field is empty — see {@link canSaveKey}.
|
|
185
|
+
*/
|
|
186
|
+
saveKey: () => Promise<void>;
|
|
187
|
+
/**
|
|
188
|
+
* Explicit save, called ONLY from a "Save settings" button press. Writes
|
|
189
|
+
* protocol/providerId/baseUrl/model/maxTokens and NEVER an `apiKey` property — not even an empty
|
|
190
|
+
* string, which the server rejects (400) and which is precisely the write the old overloaded
|
|
191
|
+
* control could make.
|
|
192
|
+
*
|
|
193
|
+
* ## Why this is not a second writer for the ledger's values
|
|
194
|
+
*
|
|
195
|
+
* The same protocol/baseUrl/model the operator edits here ALSO live in the `core.execution`
|
|
196
|
+
* settings ledger, written on their own debounced path by `use-admin-execution-mode.hooks.ts`'s
|
|
197
|
+
* `useSettingsSlice`. Two stores, deliberately (see `execution-settings.ts`'s
|
|
198
|
+
* `loadAdminExecutionCredential` doc): the ledger holds the live values this browser's form shows,
|
|
199
|
+
* while the credential ROW holds a snapshot the SERVER reads when a browser sends no credential of
|
|
200
|
+
* its own — `byok-credential.ts`'s `createStoredExecutionCredentialPort`, which treats a row with
|
|
201
|
+
* no `model` as unusable.
|
|
202
|
+
*
|
|
203
|
+
* Before the split, that snapshot was refreshed as a side effect of every key save. With
|
|
204
|
+
* {@link saveKey} narrowed to the key, this button is the ONLY thing that writes it. Each of the
|
|
205
|
+
* two stores still has exactly one writer; what changed is that the credential row's writer is now
|
|
206
|
+
* a control the operator can see and press, rather than a hidden passenger on a different button.
|
|
207
|
+
*/
|
|
208
|
+
saveSettings: () => Promise<void>;
|
|
209
|
+
/** A pre-server-store `localStorage` key this browser still holds, or `null` when there is
|
|
210
|
+
* nothing to migrate (never had one, already migrated, or the prompt was dismissed this
|
|
211
|
+
* session). Non-`null` is what a caller renders the migration banner on. */
|
|
212
|
+
legacyKey: string | null;
|
|
213
|
+
/** Confirms the migration: PUTs the legacy key (plus the current `byok` non-secret fields) to the
|
|
214
|
+
* server, and clears the `localStorage` entry ONLY once that PUT has actually succeeded — a
|
|
215
|
+
* failed save leaves the local copy fully intact, per the design's explicit "no auto-clear"
|
|
216
|
+
* requirement. */
|
|
217
|
+
migrateLegacyKey: () => Promise<void>;
|
|
218
|
+
/** Declines the prompt for the rest of this mount — no `localStorage` change either way. The
|
|
219
|
+
* design's "no auto-clear" rule means a decline must not delete the local key, so the prompt is
|
|
220
|
+
* free to reappear on a later mount (a page reload, navigating back to this tab); that is a minor
|
|
221
|
+
* repeat-ask, not a data-loss risk, which is the asymmetry the design accepts. */
|
|
222
|
+
dismissLegacyPrompt: () => void;
|
|
223
|
+
}
|
|
224
|
+
/**
|
|
225
|
+
* @complexity Time: O(1) per call — one GET on mount, one PUT per explicit save/migrate. Space:
|
|
226
|
+
* O(1) — no caller-controlled collections.
|
|
227
|
+
*/
|
|
228
|
+
export declare function useExecutionCredential<TStored extends ExecutionCredentialView>({ byok, onByokChange, locale, port, effects }: UseExecutionCredentialInput & {
|
|
229
|
+
port: ExecutionCredentialPort<TStored>;
|
|
230
|
+
effects: ExecutionCredentialEffects<TStored>;
|
|
231
|
+
}, _options?: Record<string, never>): ExecutionCredentialController<TStored>;
|
|
232
|
+
//# sourceMappingURL=use-execution-credential.hooks.d.ts.map
|