@stigmer/react 3.12.4 → 3.12.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/agent/AgentDetailView.js +2 -2
- package/agent/AgentDetailView.js.map +1 -1
- package/agent/index.d.ts +0 -1
- package/agent/index.d.ts.map +1 -1
- package/agent/index.js +0 -1
- package/agent/index.js.map +1 -1
- package/agent/useUpdateAgent.d.ts +3 -3
- package/agent/useUpdateAgent.js +3 -3
- package/agent-instance/AgentInstanceDetailPanel.d.ts.map +1 -1
- package/agent-instance/AgentInstanceDetailPanel.js +5 -7
- package/agent-instance/AgentInstanceDetailPanel.js.map +1 -1
- package/agent-instance/useUpdateAgentInstance.d.ts +1 -1
- package/agent-instance/useUpdateAgentInstance.js +1 -1
- package/attachment/AttachmentChipList.js +16 -6
- package/attachment/AttachmentChipList.js.map +1 -1
- package/attachment/index.d.ts +1 -1
- package/attachment/index.d.ts.map +1 -1
- package/attachment/index.js.map +1 -1
- package/attachment/useAttachments.d.ts +51 -8
- package/attachment/useAttachments.d.ts.map +1 -1
- package/attachment/useAttachments.js +74 -3
- package/attachment/useAttachments.js.map +1 -1
- package/channel/AgentChannelsPanel.d.ts.map +1 -1
- package/channel/AgentChannelsPanel.js +10 -5
- package/channel/AgentChannelsPanel.js.map +1 -1
- package/channel/ChannelRunConfigDialog.d.ts +51 -0
- package/channel/ChannelRunConfigDialog.d.ts.map +1 -0
- package/channel/ChannelRunConfigDialog.js +121 -0
- package/channel/ChannelRunConfigDialog.js.map +1 -0
- package/channel/index.d.ts +2 -0
- package/channel/index.d.ts.map +1 -1
- package/channel/index.js +1 -0
- package/channel/index.js.map +1 -1
- package/channel/useSaveAgentChannel.d.ts.map +1 -1
- package/channel/useSaveAgentChannel.js +6 -3
- package/channel/useSaveAgentChannel.js.map +1 -1
- package/channel-app/ChannelAppDetailPanel.js +9 -9
- package/channel-app/ChannelAppDetailPanel.js.map +1 -1
- package/composer/ComposerToolbar.d.ts +6 -1
- package/composer/ComposerToolbar.d.ts.map +1 -1
- package/composer/ComposerToolbar.js +2 -2
- package/composer/ComposerToolbar.js.map +1 -1
- package/composer/SessionComposer.d.ts +9 -0
- package/composer/SessionComposer.d.ts.map +1 -1
- package/composer/SessionComposer.js +39 -13
- package/composer/SessionComposer.js.map +1 -1
- package/conversation/ConversationComposer.d.ts +19 -3
- package/conversation/ConversationComposer.d.ts.map +1 -1
- package/conversation/ConversationComposer.js +4 -4
- package/conversation/ConversationComposer.js.map +1 -1
- package/conversation/ConversationTemplatePickerDialog.d.ts +63 -0
- package/conversation/ConversationTemplatePickerDialog.d.ts.map +1 -0
- package/conversation/ConversationTemplatePickerDialog.js +151 -0
- package/conversation/ConversationTemplatePickerDialog.js.map +1 -0
- package/conversation/ConversationsWorkbench.d.ts.map +1 -1
- package/conversation/ConversationsWorkbench.js +16 -5
- package/conversation/ConversationsWorkbench.js.map +1 -1
- package/conversation/index.d.ts +3 -1
- package/conversation/index.d.ts.map +1 -1
- package/conversation/index.js +1 -0
- package/conversation/index.js.map +1 -1
- package/conversation/useConversationParticipation.d.ts +41 -4
- package/conversation/useConversationParticipation.d.ts.map +1 -1
- package/conversation/useConversationParticipation.js +13 -2
- package/conversation/useConversationParticipation.js.map +1 -1
- package/cursor-accounts/CursorAccountsConsole.d.ts.map +1 -1
- package/cursor-accounts/CursorAccountsConsole.js +12 -3
- package/cursor-accounts/CursorAccountsConsole.js.map +1 -1
- package/cursor-accounts/cursor-account-coverage.d.ts +30 -1
- package/cursor-accounts/cursor-account-coverage.d.ts.map +1 -1
- package/cursor-accounts/cursor-account-coverage.js +21 -0
- package/cursor-accounts/cursor-account-coverage.js.map +1 -1
- package/execution/useCreateAgentExecution.d.ts +15 -0
- package/execution/useCreateAgentExecution.d.ts.map +1 -1
- package/execution/useCreateAgentExecution.js +5 -0
- package/execution/useCreateAgentExecution.js.map +1 -1
- package/identity-account/AccountPreferencesPanel.d.ts +36 -0
- package/identity-account/AccountPreferencesPanel.d.ts.map +1 -0
- package/identity-account/AccountPreferencesPanel.js +202 -0
- package/identity-account/AccountPreferencesPanel.js.map +1 -0
- package/identity-account/index.d.ts +8 -0
- package/identity-account/index.d.ts.map +1 -1
- package/identity-account/index.js +4 -0
- package/identity-account/index.js.map +1 -1
- package/identity-account/useAccountExecutionDefaults.d.ts +36 -0
- package/identity-account/useAccountExecutionDefaults.d.ts.map +1 -0
- package/identity-account/useAccountExecutionDefaults.js +46 -0
- package/identity-account/useAccountExecutionDefaults.js.map +1 -0
- package/identity-account/useMyIdentityAccount.d.ts +55 -0
- package/identity-account/useMyIdentityAccount.d.ts.map +1 -0
- package/identity-account/useMyIdentityAccount.js +37 -0
- package/identity-account/useMyIdentityAccount.js.map +1 -0
- package/identity-account/useUpdateIdentityAccount.d.ts +42 -0
- package/identity-account/useUpdateIdentityAccount.d.ts.map +1 -0
- package/identity-account/useUpdateIdentityAccount.js +53 -0
- package/identity-account/useUpdateIdentityAccount.js.map +1 -0
- package/identity-provider/IdentityProviderDetailPanel.d.ts.map +1 -1
- package/identity-provider/IdentityProviderDetailPanel.js +16 -15
- package/identity-provider/IdentityProviderDetailPanel.js.map +1 -1
- package/index.d.ts +15 -13
- package/index.d.ts.map +1 -1
- package/index.js +11 -8
- package/index.js.map +1 -1
- package/internal/StandingContextField.d.ts +37 -0
- package/internal/StandingContextField.d.ts.map +1 -0
- package/internal/StandingContextField.js +24 -0
- package/internal/StandingContextField.js.map +1 -0
- package/library/InstanceVisibilitySelector.d.ts.map +1 -1
- package/library/InstanceVisibilitySelector.js +9 -2
- package/library/InstanceVisibilitySelector.js.map +1 -1
- package/library/ResourceVisibilityControl.d.ts +13 -3
- package/library/ResourceVisibilityControl.d.ts.map +1 -1
- package/library/ResourceVisibilityControl.js +33 -5
- package/library/ResourceVisibilityControl.js.map +1 -1
- package/library/VisibilityOptionRow.d.ts +6 -0
- package/library/VisibilityOptionRow.d.ts.map +1 -1
- package/library/VisibilityOptionRow.js +11 -1
- package/library/VisibilityOptionRow.js.map +1 -1
- package/library/VisibilitySelector.d.ts.map +1 -1
- package/library/VisibilitySelector.js +14 -2
- package/library/VisibilitySelector.js.map +1 -1
- package/library/index.d.ts +4 -2
- package/library/index.d.ts.map +1 -1
- package/library/index.js +2 -1
- package/library/index.js.map +1 -1
- package/library/useCanSetPublicVisibility.d.ts +30 -0
- package/library/useCanSetPublicVisibility.d.ts.map +1 -0
- package/library/useCanSetPublicVisibility.js +41 -0
- package/library/useCanSetPublicVisibility.js.map +1 -0
- package/library/visibilityLevels.d.ts +32 -1
- package/library/visibilityLevels.d.ts.map +1 -1
- package/library/visibilityLevels.js +34 -14
- package/library/visibilityLevels.js.map +1 -1
- package/mcp-server/McpServerDetailView.js +2 -2
- package/mcp-server/McpServerDetailView.js.map +1 -1
- package/mcp-server/index.d.ts +0 -1
- package/mcp-server/index.d.ts.map +1 -1
- package/mcp-server/index.js +0 -1
- package/mcp-server/index.js.map +1 -1
- package/mcp-server/internal/definitionDrift.d.ts +14 -12
- package/mcp-server/internal/definitionDrift.d.ts.map +1 -1
- package/mcp-server/internal/definitionDrift.js +22 -19
- package/mcp-server/internal/definitionDrift.js.map +1 -1
- package/mcp-server/useMcpServerConnect.d.ts +8 -5
- package/mcp-server/useMcpServerConnect.d.ts.map +1 -1
- package/mcp-server/useMcpServerConnect.js +2 -1
- package/mcp-server/useMcpServerConnect.js.map +1 -1
- package/mcp-server/useMcpServerOAuthConnect.d.ts.map +1 -1
- package/mcp-server/useMcpServerOAuthConnect.js +7 -2
- package/mcp-server/useMcpServerOAuthConnect.js.map +1 -1
- package/mcp-server/useUpdateMcpServer.d.ts +3 -3
- package/mcp-server/useUpdateMcpServer.js +3 -3
- package/models/ModelSelector.d.ts +33 -1
- package/models/ModelSelector.d.ts.map +1 -1
- package/models/ModelSelector.js +37 -4
- package/models/ModelSelector.js.map +1 -1
- package/models/index.d.ts +2 -0
- package/models/index.d.ts.map +1 -1
- package/models/index.js +1 -0
- package/models/index.js.map +1 -1
- package/models/registry.d.ts +16 -0
- package/models/registry.d.ts.map +1 -1
- package/models/registry.js +13 -0
- package/models/registry.js.map +1 -1
- package/models/thinking-mode.d.ts +27 -0
- package/models/thinking-mode.d.ts.map +1 -0
- package/models/thinking-mode.js +32 -0
- package/models/thinking-mode.js.map +1 -0
- package/oauth-app/CreateOAuthAppForm.d.ts +2 -2
- package/oauth-app/CreateOAuthAppForm.d.ts.map +1 -1
- package/oauth-app/CreateOAuthAppForm.js +13 -4
- package/oauth-app/CreateOAuthAppForm.js.map +1 -1
- package/oauth-app/OAuthAppDetailPanel.d.ts.map +1 -1
- package/oauth-app/OAuthAppDetailPanel.js +50 -22
- package/oauth-app/OAuthAppDetailPanel.js.map +1 -1
- package/organization/OrgPreferencesPanel.d.ts +32 -0
- package/organization/OrgPreferencesPanel.d.ts.map +1 -0
- package/organization/OrgPreferencesPanel.js +90 -0
- package/organization/OrgPreferencesPanel.js.map +1 -0
- package/organization/OrgProfilePanel.d.ts.map +1 -1
- package/organization/OrgProfilePanel.js +5 -4
- package/organization/OrgProfilePanel.js.map +1 -1
- package/organization/index.d.ts +2 -0
- package/organization/index.d.ts.map +1 -1
- package/organization/index.js +1 -0
- package/organization/index.js.map +1 -1
- package/package.json +4 -4
- package/platform-client/PlatformClientDetailPanel.d.ts.map +1 -1
- package/platform-client/PlatformClientDetailPanel.js +12 -13
- package/platform-client/PlatformClientDetailPanel.js.map +1 -1
- package/schedule/ScheduleDetailView.d.ts.map +1 -1
- package/schedule/ScheduleDetailView.js +21 -10
- package/schedule/ScheduleDetailView.js.map +1 -1
- package/schedule/ScheduleForm.d.ts.map +1 -1
- package/schedule/ScheduleForm.js +13 -3
- package/schedule/ScheduleForm.js.map +1 -1
- package/session/NewSessionViewer.d.ts +10 -1
- package/session/NewSessionViewer.d.ts.map +1 -1
- package/session/NewSessionViewer.js +2 -1
- package/session/NewSessionViewer.js.map +1 -1
- package/session/run-config.d.ts +9 -0
- package/session/run-config.d.ts.map +1 -1
- package/session/run-config.js +5 -0
- package/session/run-config.js.map +1 -1
- package/session/useNewSessionFlow.d.ts +27 -1
- package/session/useNewSessionFlow.d.ts.map +1 -1
- package/session/useNewSessionFlow.js +51 -8
- package/session/useNewSessionFlow.js.map +1 -1
- package/session/useSessionConversation.d.ts +12 -1
- package/session/useSessionConversation.d.ts.map +1 -1
- package/session/useSessionConversation.js +15 -24
- package/session/useSessionConversation.js.map +1 -1
- package/session/useSessionPageFlow.d.ts.map +1 -1
- package/session/useSessionPageFlow.js +14 -10
- package/session/useSessionPageFlow.js.map +1 -1
- package/session/useUpdateSession.d.ts +18 -24
- package/session/useUpdateSession.d.ts.map +1 -1
- package/session/useUpdateSession.js +18 -24
- package/session/useUpdateSession.js.map +1 -1
- package/settings/AccountPreferencesSection.d.ts +3 -0
- package/settings/AccountPreferencesSection.d.ts.map +1 -0
- package/settings/AccountPreferencesSection.js +10 -0
- package/settings/AccountPreferencesSection.js.map +1 -0
- package/settings/OrgPreferencesSection.d.ts +3 -0
- package/settings/OrgPreferencesSection.d.ts.map +1 -0
- package/settings/OrgPreferencesSection.js +13 -0
- package/settings/OrgPreferencesSection.js.map +1 -0
- package/settings/index.d.ts +2 -0
- package/settings/index.d.ts.map +1 -1
- package/settings/index.js +2 -0
- package/settings/index.js.map +1 -1
- package/settings/settings-nav.d.ts.map +1 -1
- package/settings/settings-nav.js +17 -1
- package/settings/settings-nav.js.map +1 -1
- package/src/agent/AgentDetailView.tsx +2 -2
- package/src/agent/index.ts +0 -2
- package/src/agent/useUpdateAgent.ts +3 -3
- package/src/agent-instance/AgentInstanceDetailPanel.tsx +5 -7
- package/src/agent-instance/__tests__/AgentInstanceDetailPanel.test.tsx +75 -0
- package/src/agent-instance/useUpdateAgentInstance.ts +1 -1
- package/src/attachment/AttachmentChipList.tsx +19 -8
- package/src/attachment/__tests__/AttachmentChipList.test.tsx +12 -0
- package/src/attachment/__tests__/useAttachments.test.ts +293 -0
- package/src/attachment/index.ts +1 -0
- package/src/attachment/useAttachments.ts +143 -11
- package/src/channel/AgentChannelsPanel.tsx +26 -1
- package/src/channel/ChannelRunConfigDialog.tsx +351 -0
- package/src/channel/__tests__/ChannelRunConfigDialog.test.tsx +210 -0
- package/src/channel/index.ts +2 -0
- package/src/channel/useSaveAgentChannel.ts +6 -3
- package/src/channel-app/ChannelAppDetailPanel.tsx +9 -9
- package/src/channel-app/__tests__/channelAppWhatsApp.test.tsx +2 -0
- package/src/channel-app/__tests__/channelAppWipeSafety.test.tsx +87 -0
- package/src/composer/ComposerToolbar.tsx +9 -0
- package/src/composer/SessionComposer.tsx +59 -16
- package/src/composer/__tests__/SessionComposer-thinkingMode.test.tsx +176 -0
- package/src/conversation/ConversationComposer.tsx +38 -4
- package/src/conversation/ConversationTemplatePickerDialog.tsx +441 -0
- package/src/conversation/ConversationsWorkbench.tsx +40 -4
- package/src/conversation/__tests__/ConversationComposer.test.tsx +56 -1
- package/src/conversation/__tests__/ConversationTemplatePickerDialog.test.tsx +224 -0
- package/src/conversation/__tests__/useConversationParticipation.test.tsx +45 -4
- package/src/conversation/index.ts +4 -0
- package/src/conversation/useConversationParticipation.ts +58 -6
- package/src/cursor-accounts/CursorAccountsConsole.tsx +40 -5
- package/src/cursor-accounts/__tests__/CursorAccountsConsole.test.tsx +32 -0
- package/src/cursor-accounts/__tests__/cursor-account-coverage.test.ts +58 -1
- package/src/cursor-accounts/cursor-account-coverage.ts +55 -0
- package/src/execution/useCreateAgentExecution.ts +19 -0
- package/src/identity-account/AccountPreferencesPanel.tsx +532 -0
- package/src/identity-account/__tests__/AccountPreferencesPanel.test.tsx +267 -0
- package/src/identity-account/__tests__/useAccountExecutionDefaults.test.tsx +116 -0
- package/src/identity-account/__tests__/useMyIdentityAccount.test.tsx +121 -0
- package/src/identity-account/index.ts +11 -0
- package/src/identity-account/useAccountExecutionDefaults.ts +63 -0
- package/src/identity-account/useMyIdentityAccount.ts +75 -0
- package/src/identity-account/useUpdateIdentityAccount.ts +73 -0
- package/src/identity-provider/IdentityProviderDetailPanel.tsx +18 -15
- package/src/identity-provider/__tests__/IdentityProviderDetailPanel.test.tsx +80 -0
- package/src/index.ts +26 -4
- package/src/internal/StandingContextField.tsx +92 -0
- package/src/library/InstanceVisibilitySelector.tsx +9 -2
- package/src/library/ResourceVisibilityControl.tsx +46 -13
- package/src/library/VisibilityOptionRow.tsx +28 -6
- package/src/library/VisibilitySelector.tsx +14 -2
- package/src/library/__tests__/VisibilitySelector.test.tsx +48 -1
- package/src/library/__tests__/visibilityLevels.test.ts +67 -1
- package/src/library/index.ts +6 -1
- package/src/library/useCanSetPublicVisibility.ts +56 -0
- package/src/library/visibilityLevels.ts +65 -14
- package/src/mcp-server/McpServerDetailView.tsx +2 -2
- package/src/mcp-server/__tests__/useMcpServerConnect.asyncLane.test.tsx +117 -0
- package/src/mcp-server/index.ts +0 -2
- package/src/mcp-server/internal/definitionDrift.ts +22 -20
- package/src/mcp-server/useMcpServerConnect.ts +9 -6
- package/src/mcp-server/useMcpServerOAuthConnect.ts +7 -2
- package/src/mcp-server/useUpdateMcpServer.ts +3 -3
- package/src/models/ModelSelector.tsx +94 -1
- package/src/models/index.ts +2 -0
- package/src/models/registry.ts +29 -0
- package/src/models/thinking-mode.ts +48 -0
- package/src/oauth-app/CreateOAuthAppForm.tsx +58 -3
- package/src/oauth-app/OAuthAppDetailPanel.tsx +103 -20
- package/src/oauth-app/__tests__/OAuthAppDetailPanel.test.tsx +94 -0
- package/src/organization/OrgPreferencesPanel.tsx +221 -0
- package/src/organization/OrgProfilePanel.tsx +5 -4
- package/src/organization/__tests__/OrgPreferencesPanel.test.tsx +145 -0
- package/src/organization/__tests__/OrgProfilePanel.test.tsx +108 -0
- package/src/organization/index.ts +2 -0
- package/src/platform-client/PlatformClientDetailPanel.tsx +14 -13
- package/src/platform-client/__tests__/PlatformClientDetailPanel.test.tsx +82 -0
- package/src/schedule/ScheduleDetailView.tsx +29 -4
- package/src/schedule/ScheduleForm.tsx +14 -1
- package/src/session/NewSessionViewer.tsx +12 -0
- package/src/session/__tests__/useNewSessionFlow.test.tsx +151 -0
- package/src/session/run-config.ts +16 -0
- package/src/session/useNewSessionFlow.ts +82 -9
- package/src/session/useSessionConversation.ts +32 -40
- package/src/session/useSessionPageFlow.ts +14 -11
- package/src/session/useUpdateSession.ts +18 -24
- package/src/settings/AccountPreferencesSection.tsx +25 -0
- package/src/settings/OrgPreferencesSection.tsx +35 -0
- package/src/settings/__tests__/settings-nav.test.ts +27 -0
- package/src/settings/index.ts +2 -0
- package/src/settings/settings-nav.ts +18 -0
- package/src/workflow/ApprovalFormBuilder.tsx +20 -27
- package/src/workflow/WorkflowDetailView.tsx +2 -2
- package/src/workflow/__tests__/WorkflowInstanceDetailPanel.test.tsx +83 -0
- package/src/workflow/index.ts +0 -2
- package/src/workflow/instance/WorkflowInstanceDetailPanel.tsx +6 -7
- package/src/workflow/useUpdateWorkflow.ts +3 -3
- package/styles.css +1 -1
- package/workflow/ApprovalFormBuilder.d.ts.map +1 -1
- package/workflow/ApprovalFormBuilder.js +10 -5
- package/workflow/ApprovalFormBuilder.js.map +1 -1
- package/workflow/WorkflowDetailView.js +2 -2
- package/workflow/WorkflowDetailView.js.map +1 -1
- package/workflow/index.d.ts +0 -1
- package/workflow/index.d.ts.map +1 -1
- package/workflow/index.js +0 -1
- package/workflow/index.js.map +1 -1
- package/workflow/instance/WorkflowInstanceDetailPanel.d.ts.map +1 -1
- package/workflow/instance/WorkflowInstanceDetailPanel.js +6 -7
- package/workflow/instance/WorkflowInstanceDetailPanel.js.map +1 -1
- package/workflow/useUpdateWorkflow.d.ts +3 -3
- package/workflow/useUpdateWorkflow.js +3 -3
- package/agent/internal/agentToInput.d.ts +0 -15
- package/agent/internal/agentToInput.d.ts.map +0 -1
- package/agent/internal/agentToInput.js +0 -84
- package/agent/internal/agentToInput.js.map +0 -1
- package/mcp-server/internal/mcpServerToInput.d.ts +0 -12
- package/mcp-server/internal/mcpServerToInput.d.ts.map +0 -1
- package/mcp-server/internal/mcpServerToInput.js +0 -84
- package/mcp-server/internal/mcpServerToInput.js.map +0 -1
- package/session/session-spec-converters.d.ts +0 -24
- package/session/session-spec-converters.d.ts.map +0 -1
- package/session/session-spec-converters.js +0 -72
- package/session/session-spec-converters.js.map +0 -1
- package/src/agent/internal/__tests__/agentToInput.test.ts +0 -145
- package/src/agent/internal/agentToInput.ts +0 -102
- package/src/mcp-server/__tests__/mcpServerToInput.test.ts +0 -201
- package/src/mcp-server/internal/mcpServerToInput.ts +0 -98
- package/src/session/__tests__/session-spec-converters.test.ts +0 -183
- package/src/session/session-spec-converters.ts +0 -86
- package/src/workflow/internal/workflowToInput.ts +0 -96
- package/workflow/internal/workflowToInput.d.ts +0 -12
- package/workflow/internal/workflowToInput.d.ts.map +0 -1
- package/workflow/internal/workflowToInput.js +0 -73
- package/workflow/internal/workflowToInput.js.map +0 -1
|
@@ -9,6 +9,7 @@ import { useModelRegistry } from "./useModelRegistry.js";
|
|
|
9
9
|
import type { ModelInfo, CostTier, SpeedTier } from "./registry.js";
|
|
10
10
|
import { HARNESS_META, HARNESS_OPTIONS, type HarnessOption } from "./harness.js";
|
|
11
11
|
import { FAST_SERVICE_TIER, type ServiceTierOption } from "./service-tier.js";
|
|
12
|
+
import type { ThinkingModeOption } from "./thinking-mode.js";
|
|
12
13
|
|
|
13
14
|
const COST_TIER_LABEL: Record<CostTier, string> = {
|
|
14
15
|
economy: "$",
|
|
@@ -23,6 +24,17 @@ const SPEED_TIER_LABEL: Record<SpeedTier, string> = {
|
|
|
23
24
|
slow: "Powerful",
|
|
24
25
|
};
|
|
25
26
|
|
|
27
|
+
/**
|
|
28
|
+
* Whether the thinking switch may act on this model (stigmer/stigmer#772):
|
|
29
|
+
* the registry declares the capability AND the model is cursor-harness —
|
|
30
|
+
* the only harness with a thinking translation in v1. Native models
|
|
31
|
+
* truthfully declare the capability too, but selecting it there would be
|
|
32
|
+
* refused at create time, so the control never renders for them.
|
|
33
|
+
*/
|
|
34
|
+
function supportsThinking(model: ModelInfo): boolean {
|
|
35
|
+
return model.harness === "cursor" && model.thinkingCapable === true;
|
|
36
|
+
}
|
|
37
|
+
|
|
26
38
|
/** Props for {@link ModelSelector}. */
|
|
27
39
|
export interface ModelSelectorProps {
|
|
28
40
|
/** Currently selected model ID. */
|
|
@@ -95,6 +107,30 @@ export interface ModelSelectorProps {
|
|
|
95
107
|
*/
|
|
96
108
|
readonly onServiceTierChange?: (tier: ServiceTierOption) => void;
|
|
97
109
|
|
|
110
|
+
/**
|
|
111
|
+
* Current thinking mode for the selected model (stigmer/stigmer#772).
|
|
112
|
+
* Defaults to "disabled". Only meaningful together with
|
|
113
|
+
* {@link onThinkingModeChange}.
|
|
114
|
+
*/
|
|
115
|
+
readonly thinkingMode?: ThinkingModeOption;
|
|
116
|
+
/**
|
|
117
|
+
* Called when the user toggles extended reasoning. Providing this enables
|
|
118
|
+
* the "Thinking" switch as a sibling of the fast-tier switch in the
|
|
119
|
+
* popover's options area, which renders ONLY while the selected model is
|
|
120
|
+
* a cursor-harness model declaring the thinking capability
|
|
121
|
+
* ({@link ModelInfo.thinkingCapable}) — no dead controls, and v1 honors
|
|
122
|
+
* the selection on the cursor harness only.
|
|
123
|
+
*
|
|
124
|
+
* The same persistence rule as the fast tier: an active thinking mode
|
|
125
|
+
* survives switches between thinking-capable models (trigger badge +
|
|
126
|
+
* options switch keep it visible) and resets to "disabled" only when the
|
|
127
|
+
* new model lacks the capability — an unsupported combination can never
|
|
128
|
+
* be submitted (#772, refused at create time). Unlike fast, thinking
|
|
129
|
+
* bills at base per-token rates; enabled turns consume more output
|
|
130
|
+
* (reasoning) tokens.
|
|
131
|
+
*/
|
|
132
|
+
readonly onThinkingModeChange?: (mode: ThinkingModeOption) => void;
|
|
133
|
+
|
|
98
134
|
/**
|
|
99
135
|
* @deprecated Use {@link onHarnessChange} instead.
|
|
100
136
|
*/
|
|
@@ -125,6 +161,14 @@ export interface ModelSelectorProps {
|
|
|
125
161
|
* switch keep the state honest — and resets only for models with no fast
|
|
126
162
|
* variant, where it would be refused at create time.
|
|
127
163
|
*
|
|
164
|
+
* **Thinking mode (#772).** The same options-area pattern for the second
|
|
165
|
+
* variant dimension: opting in via
|
|
166
|
+
* {@link ModelSelectorProps.onThinkingModeChange} renders a "Thinking"
|
|
167
|
+
* switch for cursor-harness models declaring the thinking capability.
|
|
168
|
+
* Capability-gated rather than priced-variant-gated — thinking bills at
|
|
169
|
+
* base per-token rates and costs more only through extra reasoning
|
|
170
|
+
* (output) tokens. Persistence and reset rules mirror the fast tier's.
|
|
171
|
+
*
|
|
128
172
|
* @example
|
|
129
173
|
* ```tsx
|
|
130
174
|
* <ModelSelector
|
|
@@ -153,6 +197,8 @@ export function ModelSelector({
|
|
|
153
197
|
placeholderLabel,
|
|
154
198
|
serviceTier = "standard",
|
|
155
199
|
onServiceTierChange,
|
|
200
|
+
thinkingMode = "disabled",
|
|
201
|
+
onThinkingModeChange,
|
|
156
202
|
}: ModelSelectorProps) {
|
|
157
203
|
const portalContainer = useStigmerPortalContainer();
|
|
158
204
|
|
|
@@ -277,9 +323,19 @@ export function ModelSelector({
|
|
|
277
323
|
) {
|
|
278
324
|
onServiceTierChange("standard");
|
|
279
325
|
}
|
|
326
|
+
// The identical persistence rule for the thinking mode (#772):
|
|
327
|
+
// survives between thinking-capable cursor models, resets only
|
|
328
|
+
// where the selection would be refused at create time.
|
|
329
|
+
if (
|
|
330
|
+
thinkingMode === "enabled"
|
|
331
|
+
&& onThinkingModeChange
|
|
332
|
+
&& !supportsThinking(model)
|
|
333
|
+
) {
|
|
334
|
+
onThinkingModeChange("disabled");
|
|
335
|
+
}
|
|
280
336
|
setOpen(false);
|
|
281
337
|
},
|
|
282
|
-
[onValueChange, serviceTier, onServiceTierChange],
|
|
338
|
+
[onValueChange, serviceTier, onServiceTierChange, thinkingMode, onThinkingModeChange],
|
|
283
339
|
);
|
|
284
340
|
|
|
285
341
|
const scrollHighlightIntoView = useCallback((idx: number) => {
|
|
@@ -338,6 +394,15 @@ export function ModelSelector({
|
|
|
338
394
|
const fastActive =
|
|
339
395
|
serviceTier === "fast"
|
|
340
396
|
&& (selectedModel?.serviceTiers.includes(FAST_SERVICE_TIER) ?? false);
|
|
397
|
+
// The thinking switch renders under the same no-dead-controls rule,
|
|
398
|
+
// gated on the capability instead of a priced variant (#772).
|
|
399
|
+
const showThinkingToggle =
|
|
400
|
+
onThinkingModeChange !== undefined
|
|
401
|
+
&& selectedModel !== undefined
|
|
402
|
+
&& supportsThinking(selectedModel);
|
|
403
|
+
const thinkingActive =
|
|
404
|
+
thinkingMode === "enabled"
|
|
405
|
+
&& (selectedModel !== undefined && supportsThinking(selectedModel));
|
|
341
406
|
|
|
342
407
|
const usingPlaceholder = !value && placeholderLabel !== undefined;
|
|
343
408
|
const triggerLabel = usingPlaceholder
|
|
@@ -379,6 +444,16 @@ export function ModelSelector({
|
|
|
379
444
|
Fast
|
|
380
445
|
</span>
|
|
381
446
|
)}
|
|
447
|
+
{thinkingActive && (
|
|
448
|
+
<span
|
|
449
|
+
className={cn(
|
|
450
|
+
"stg:inline-flex stg:shrink-0 stg:items-center stg:gap-0.5 stg:rounded-full stg:border stg:border-primary stg:px-1.5 stg:py-px",
|
|
451
|
+
"stg:text-[0.6rem] stg:font-medium stg:uppercase stg:tracking-wider stg:text-primary",
|
|
452
|
+
)}
|
|
453
|
+
>
|
|
454
|
+
Thinking
|
|
455
|
+
</span>
|
|
456
|
+
)}
|
|
382
457
|
<ChevronIcon />
|
|
383
458
|
</Popover.Trigger>
|
|
384
459
|
|
|
@@ -483,6 +558,24 @@ export function ModelSelector({
|
|
|
483
558
|
</div>
|
|
484
559
|
)}
|
|
485
560
|
|
|
561
|
+
{showThinkingToggle && (
|
|
562
|
+
<div className="stg:flex stg:items-center stg:justify-between stg:border-b stg:border-border stg:px-3 stg:py-2">
|
|
563
|
+
<div className="stg:flex stg:flex-col">
|
|
564
|
+
<span className="stg:text-xs stg:text-foreground">Thinking</span>
|
|
565
|
+
<span className="stg:text-[0.65rem] stg:text-muted-foreground">
|
|
566
|
+
Extended reasoning; uses more output tokens
|
|
567
|
+
</span>
|
|
568
|
+
</div>
|
|
569
|
+
<Switch
|
|
570
|
+
checked={thinkingActive}
|
|
571
|
+
onCheckedChange={(next) =>
|
|
572
|
+
onThinkingModeChange?.(next ? "enabled" : "disabled")
|
|
573
|
+
}
|
|
574
|
+
aria-label="Thinking"
|
|
575
|
+
/>
|
|
576
|
+
</div>
|
|
577
|
+
)}
|
|
578
|
+
|
|
486
579
|
{/* Search input */}
|
|
487
580
|
<div className="stg:border-b stg:border-border stg:px-3 stg:py-1.5">
|
|
488
581
|
<input
|
package/src/models/index.ts
CHANGED
|
@@ -13,3 +13,5 @@ export { DEFAULT_HARNESS, HARNESS_LABELS, HARNESS_META, HARNESS_OPTIONS, toProto
|
|
|
13
13
|
export type { HarnessOption, HarnessDisplayInfo } from "./harness.js";
|
|
14
14
|
export { FAST_SERVICE_TIER, toProtoServiceTier, fromProtoServiceTier } from "./service-tier.js";
|
|
15
15
|
export type { ServiceTierOption } from "./service-tier.js";
|
|
16
|
+
export { toProtoThinkingMode, fromProtoThinkingMode } from "./thinking-mode.js";
|
|
17
|
+
export type { ThinkingModeOption } from "./thinking-mode.js";
|
package/src/models/registry.ts
CHANGED
|
@@ -114,6 +114,22 @@ export interface ModelInfo {
|
|
|
114
114
|
* parse the same document and must agree on what absence means.
|
|
115
115
|
*/
|
|
116
116
|
readonly visionCapability?: boolean;
|
|
117
|
+
/**
|
|
118
|
+
* Tri-state thinking capability from the registry's `capabilities` block
|
|
119
|
+
* (stigmer/stigmer#772), the same absence convention as
|
|
120
|
+
* {@link visionCapability}:
|
|
121
|
+
*
|
|
122
|
+
* - `true` — the model declares an extended-reasoning (thinking) variant;
|
|
123
|
+
* the thinking switch in the model popover's options area may render
|
|
124
|
+
* - `false` — explicitly assessed as having none
|
|
125
|
+
* - `undefined` — never assessed; **stay silent, never treat as false**
|
|
126
|
+
*
|
|
127
|
+
* Capability-gated, not pricing-gated (unlike {@link serviceTiers}):
|
|
128
|
+
* thinking bills at base per-token rates, so no priced variant exists to
|
|
129
|
+
* key on. v1 honors the selection on the cursor harness only — gate any
|
|
130
|
+
* control on `harness === "cursor"` as well as this flag.
|
|
131
|
+
*/
|
|
132
|
+
readonly thinkingCapable?: boolean;
|
|
117
133
|
}
|
|
118
134
|
|
|
119
135
|
/**
|
|
@@ -229,6 +245,17 @@ function parseVisionCapability(capabilities: unknown): boolean | undefined {
|
|
|
229
245
|
return typeof vision === "boolean" ? vision : undefined;
|
|
230
246
|
}
|
|
231
247
|
|
|
248
|
+
/**
|
|
249
|
+
* Extract `capabilities.thinking` with the same tri-state convention as
|
|
250
|
+
* {@link parseVisionCapability} (stigmer/stigmer#772): a missing or
|
|
251
|
+
* malformed `capabilities` block stays `undefined`, never coerced to false.
|
|
252
|
+
*/
|
|
253
|
+
function parseThinkingCapability(capabilities: unknown): boolean | undefined {
|
|
254
|
+
if (!capabilities || typeof capabilities !== "object") return undefined;
|
|
255
|
+
const thinking = (capabilities as Record<string, unknown>).thinking;
|
|
256
|
+
return typeof thinking === "boolean" ? thinking : undefined;
|
|
257
|
+
}
|
|
258
|
+
|
|
232
259
|
/**
|
|
233
260
|
* Parse the document-level `limits.vision` block. Returns `undefined` for
|
|
234
261
|
* documents that predate the block or carry a malformed one — absence
|
|
@@ -284,6 +311,7 @@ export function parseRegistryDocument(data: unknown): ModelRegistryDocument {
|
|
|
284
311
|
.filter(isModelEntry)
|
|
285
312
|
.map((m) => {
|
|
286
313
|
const visionCapability = parseVisionCapability(m.capabilities);
|
|
314
|
+
const thinkingCapable = parseThinkingCapability(m.capabilities);
|
|
287
315
|
return {
|
|
288
316
|
modelId: m.id,
|
|
289
317
|
provider: m.provider as Provider,
|
|
@@ -300,6 +328,7 @@ export function parseRegistryDocument(data: unknown): ModelRegistryDocument {
|
|
|
300
328
|
// Conditional spread keeps unassessed models free of the key
|
|
301
329
|
// (tri-state absence, not an explicit undefined).
|
|
302
330
|
...(visionCapability !== undefined ? { visionCapability } : {}),
|
|
331
|
+
...(thinkingCapable !== undefined ? { thinkingCapable } : {}),
|
|
303
332
|
};
|
|
304
333
|
});
|
|
305
334
|
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { ThinkingMode } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/enum_pb";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* String form of the thinking mode for component props and hook inputs
|
|
5
|
+
* (stigmer/stigmer#772). Mirrors {@link ServiceTierOption}: components
|
|
6
|
+
* speak strings, execution creation speaks proto.
|
|
7
|
+
*
|
|
8
|
+
* "disabled" is the model's base variant; "enabled" is the model's
|
|
9
|
+
* extended-reasoning variant — selectable only for models whose
|
|
10
|
+
* {@link ModelInfo.thinkingCapable} is true. Unlike the fast tier,
|
|
11
|
+
* thinking bills at base per-token rates; enabled turns simply consume
|
|
12
|
+
* more output (reasoning) tokens.
|
|
13
|
+
*/
|
|
14
|
+
export type ThinkingModeOption = "disabled" | "enabled";
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Convert a {@link ThinkingModeOption} string to the proto enum.
|
|
18
|
+
*/
|
|
19
|
+
export function toProtoThinkingMode(mode: ThinkingModeOption): ThinkingMode {
|
|
20
|
+
switch (mode) {
|
|
21
|
+
case "enabled":
|
|
22
|
+
return ThinkingMode.ENABLED;
|
|
23
|
+
case "disabled":
|
|
24
|
+
default:
|
|
25
|
+
return ThinkingMode.DISABLED;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Convert a proto {@link ThinkingMode} enum to its string form.
|
|
31
|
+
*
|
|
32
|
+
* `UNSPECIFIED` (and any unknown value) maps to `undefined` so callers can
|
|
33
|
+
* decide their own default — matching the proto contract (`UNSPECIFIED`
|
|
34
|
+
* resolves to `THINKING_MODE_DISABLED` in the runner, never the provider
|
|
35
|
+
* account default).
|
|
36
|
+
*/
|
|
37
|
+
export function fromProtoThinkingMode(
|
|
38
|
+
mode: ThinkingMode | undefined,
|
|
39
|
+
): ThinkingModeOption | undefined {
|
|
40
|
+
switch (mode) {
|
|
41
|
+
case ThinkingMode.DISABLED:
|
|
42
|
+
return "disabled";
|
|
43
|
+
case ThinkingMode.ENABLED:
|
|
44
|
+
return "enabled";
|
|
45
|
+
default:
|
|
46
|
+
return undefined;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
@@ -4,7 +4,10 @@ import { type FormEvent, useCallback, useId, useState } from "react";
|
|
|
4
4
|
import { cn } from "@stigmer/theme";
|
|
5
5
|
import { getUserMessage } from "@stigmer/sdk";
|
|
6
6
|
import type { OAuthApp } from "@stigmer/protos/ai/stigmer/iam/oauthapp/v1/api_pb";
|
|
7
|
-
import {
|
|
7
|
+
import {
|
|
8
|
+
TokenEndpointAuthMethod,
|
|
9
|
+
VendorApprovalStatus,
|
|
10
|
+
} from "@stigmer/protos/ai/stigmer/iam/oauthapp/v1/spec_pb";
|
|
8
11
|
import { useCreateOAuthApp } from "./useCreateOAuthApp.js";
|
|
9
12
|
import { SpinnerIcon } from "../internal/SpinnerIcon.js";
|
|
10
13
|
|
|
@@ -30,8 +33,8 @@ export interface CreateOAuthAppFormProps {
|
|
|
30
33
|
* Collects the required OAuth configuration: **name**, **provider**,
|
|
31
34
|
* **client ID**, **client secret**, **authorization URL**, and
|
|
32
35
|
* **token URL**. An expandable "Advanced" section provides optional
|
|
33
|
-
* fields for scopes, userinfo URL, scope parameter name,
|
|
34
|
-
* approval settings.
|
|
36
|
+
* fields for scopes, userinfo URL, scope parameter name, token endpoint
|
|
37
|
+
* auth method, and vendor approval settings.
|
|
35
38
|
*
|
|
36
39
|
* This is a pure presentational component with no dialog wrapper
|
|
37
40
|
* (headless-first). The parent is responsible for rendering it inside
|
|
@@ -71,6 +74,9 @@ export function CreateOAuthAppForm({
|
|
|
71
74
|
const [scopes, setScopes] = useState("");
|
|
72
75
|
const [userinfoUrl, setUserinfoUrl] = useState("");
|
|
73
76
|
const [scopeParameterName, setScopeParameterName] = useState("");
|
|
77
|
+
const [tokenEndpointAuthMethod, setTokenEndpointAuthMethod] = useState<
|
|
78
|
+
"unspecified" | "client_secret_basic" | "client_secret_post"
|
|
79
|
+
>("unspecified");
|
|
74
80
|
const [vendorApprovalStatus, setVendorApprovalStatus] = useState<
|
|
75
81
|
"unspecified" | "pending" | "approved" | "rejected"
|
|
76
82
|
>("unspecified");
|
|
@@ -117,6 +123,10 @@ export function CreateOAuthAppForm({
|
|
|
117
123
|
...(scopeParameterName.trim() && {
|
|
118
124
|
scopeParameterName: scopeParameterName.trim(),
|
|
119
125
|
}),
|
|
126
|
+
...(tokenEndpointAuthMethod !== "unspecified" && {
|
|
127
|
+
tokenEndpointAuthMethod:
|
|
128
|
+
TOKEN_AUTH_METHOD_MAP[tokenEndpointAuthMethod],
|
|
129
|
+
}),
|
|
120
130
|
...(vendorApprovalStatus !== "unspecified" && {
|
|
121
131
|
vendorApprovalStatus: APPROVAL_STATUS_MAP[vendorApprovalStatus],
|
|
122
132
|
}),
|
|
@@ -141,6 +151,7 @@ export function CreateOAuthAppForm({
|
|
|
141
151
|
scopes,
|
|
142
152
|
userinfoUrl,
|
|
143
153
|
scopeParameterName,
|
|
154
|
+
tokenEndpointAuthMethod,
|
|
144
155
|
vendorApprovalStatus,
|
|
145
156
|
vendorApprovalDocsUrl,
|
|
146
157
|
create,
|
|
@@ -259,6 +270,45 @@ export function CreateOAuthAppForm({
|
|
|
259
270
|
disabled={isCreating}
|
|
260
271
|
/>
|
|
261
272
|
|
|
273
|
+
<div className="stg:space-y-1">
|
|
274
|
+
<label
|
|
275
|
+
htmlFor={`${baseId}-token-auth-method`}
|
|
276
|
+
className="stg:text-xs stg:font-medium stg:text-foreground"
|
|
277
|
+
>
|
|
278
|
+
Token endpoint auth method
|
|
279
|
+
</label>
|
|
280
|
+
<select
|
|
281
|
+
id={`${baseId}-token-auth-method`}
|
|
282
|
+
value={tokenEndpointAuthMethod}
|
|
283
|
+
onChange={(e) =>
|
|
284
|
+
setTokenEndpointAuthMethod(
|
|
285
|
+
e.target.value as typeof tokenEndpointAuthMethod,
|
|
286
|
+
)
|
|
287
|
+
}
|
|
288
|
+
disabled={isCreating}
|
|
289
|
+
className={cn(
|
|
290
|
+
"stg:w-full stg:rounded-md stg:border stg:border-input stg:bg-background stg:px-2.5 stg:py-1.5 stg:text-xs stg:text-foreground",
|
|
291
|
+
"stg:focus-visible:outline-none stg:focus-visible:ring-1 stg:focus-visible:ring-ring",
|
|
292
|
+
"stg:disabled:pointer-events-none stg:disabled:opacity-50",
|
|
293
|
+
)}
|
|
294
|
+
>
|
|
295
|
+
<option value="unspecified">
|
|
296
|
+
Unspecified (treated as HTTP Basic)
|
|
297
|
+
</option>
|
|
298
|
+
<option value="client_secret_basic">
|
|
299
|
+
client_secret_basic (HTTP Basic header)
|
|
300
|
+
</option>
|
|
301
|
+
<option value="client_secret_post">
|
|
302
|
+
client_secret_post (form body)
|
|
303
|
+
</option>
|
|
304
|
+
</select>
|
|
305
|
+
<p className="stg:text-[0.65rem] stg:text-muted-foreground">
|
|
306
|
+
How the client secret is sent to the vendor's token
|
|
307
|
+
endpoint. Most vendors accept HTTP Basic; some (e.g.
|
|
308
|
+
HubSpot) require client_secret_post.
|
|
309
|
+
</p>
|
|
310
|
+
</div>
|
|
311
|
+
|
|
262
312
|
<div className="stg:space-y-1">
|
|
263
313
|
<label
|
|
264
314
|
htmlFor={`${baseId}-approval-status`}
|
|
@@ -354,6 +404,11 @@ const APPROVAL_STATUS_MAP = {
|
|
|
354
404
|
rejected: VendorApprovalStatus.REJECTED,
|
|
355
405
|
} as const;
|
|
356
406
|
|
|
407
|
+
const TOKEN_AUTH_METHOD_MAP = {
|
|
408
|
+
client_secret_basic: TokenEndpointAuthMethod.CLIENT_SECRET_BASIC,
|
|
409
|
+
client_secret_post: TokenEndpointAuthMethod.CLIENT_SECRET_POST,
|
|
410
|
+
} as const;
|
|
411
|
+
|
|
357
412
|
// ---------------------------------------------------------------------------
|
|
358
413
|
// FormField (internal)
|
|
359
414
|
// ---------------------------------------------------------------------------
|
|
@@ -2,9 +2,12 @@
|
|
|
2
2
|
|
|
3
3
|
import { type FormEvent, useCallback, useId, useState } from "react";
|
|
4
4
|
import { cn } from "@stigmer/theme";
|
|
5
|
-
import { getUserMessage } from "@stigmer/sdk";
|
|
5
|
+
import { getUserMessage, toOAuthAppUpdateInput } from "@stigmer/sdk";
|
|
6
6
|
import type { OAuthApp } from "@stigmer/protos/ai/stigmer/iam/oauthapp/v1/api_pb";
|
|
7
|
-
import {
|
|
7
|
+
import {
|
|
8
|
+
TokenEndpointAuthMethod,
|
|
9
|
+
VendorApprovalStatus,
|
|
10
|
+
} from "@stigmer/protos/ai/stigmer/iam/oauthapp/v1/spec_pb";
|
|
8
11
|
import { timestampDate, type Timestamp } from "@bufbuild/protobuf/wkt";
|
|
9
12
|
import { useUpdateOAuthApp } from "./useUpdateOAuthApp.js";
|
|
10
13
|
import { useDeleteOAuthApp } from "./useDeleteOAuthApp.js";
|
|
@@ -87,6 +90,9 @@ export function OAuthAppDetailPanel({
|
|
|
87
90
|
const [scopeParameterName, setScopeParameterName] = useState(
|
|
88
91
|
spec?.scopeParameterName ?? "",
|
|
89
92
|
);
|
|
93
|
+
const [tokenEndpointAuthMethod, setTokenEndpointAuthMethod] = useState(
|
|
94
|
+
tokenAuthMethodToKey(spec?.tokenEndpointAuthMethod),
|
|
95
|
+
);
|
|
90
96
|
const [vendorApprovalStatus, setVendorApprovalStatus] = useState(
|
|
91
97
|
approvalStatusToKey(spec?.vendorApprovalStatus),
|
|
92
98
|
);
|
|
@@ -103,6 +109,7 @@ export function OAuthAppDetailPanel({
|
|
|
103
109
|
setScopes(spec?.scopes.join(", ") ?? "");
|
|
104
110
|
setUserinfoUrl(spec?.userinfoUrl ?? "");
|
|
105
111
|
setScopeParameterName(spec?.scopeParameterName ?? "");
|
|
112
|
+
setTokenEndpointAuthMethod(tokenAuthMethodToKey(spec?.tokenEndpointAuthMethod));
|
|
106
113
|
setVendorApprovalStatus(approvalStatusToKey(spec?.vendorApprovalStatus));
|
|
107
114
|
setVendorApprovalDocsUrl(spec?.vendorApprovalDocsUrl ?? "");
|
|
108
115
|
clearUpdateError();
|
|
@@ -125,26 +132,32 @@ export function OAuthAppDetailPanel({
|
|
|
125
132
|
.filter(Boolean);
|
|
126
133
|
|
|
127
134
|
try {
|
|
135
|
+
// Full-spec-replace safety: spread the complete mapped input and
|
|
136
|
+
// override every field this form owns, so a future spec field the
|
|
137
|
+
// form does not know about survives the save. An empty secret
|
|
138
|
+
// input keeps the fetched value — the redaction marker — which
|
|
139
|
+
// the server's update pipeline treats as "keep the stored
|
|
140
|
+
// secret"; a non-empty input replaces it.
|
|
141
|
+
const mapped = toOAuthAppUpdateInput(oauthApp);
|
|
128
142
|
const updated = await update({
|
|
129
|
-
|
|
130
|
-
slug: meta?.slug,
|
|
131
|
-
org: meta?.org ?? "",
|
|
143
|
+
...mapped,
|
|
132
144
|
provider: provider.trim(),
|
|
133
145
|
clientId: clientId.trim(),
|
|
134
|
-
|
|
146
|
+
clientSecret: clientSecret.trim() || mapped.clientSecret,
|
|
135
147
|
authorizationUrl: authorizationUrl.trim(),
|
|
136
148
|
tokenUrl: tokenUrl.trim(),
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
149
|
+
scopes: parsedScopes.length > 0 ? parsedScopes : undefined,
|
|
150
|
+
userinfoUrl: userinfoUrl.trim() || undefined,
|
|
151
|
+
scopeParameterName: scopeParameterName.trim() || undefined,
|
|
152
|
+
tokenEndpointAuthMethod:
|
|
153
|
+
tokenEndpointAuthMethod !== "unspecified"
|
|
154
|
+
? TOKEN_AUTH_METHOD_MAP[tokenEndpointAuthMethod]
|
|
155
|
+
: undefined,
|
|
156
|
+
vendorApprovalStatus:
|
|
157
|
+
vendorApprovalStatus !== "unspecified"
|
|
158
|
+
? APPROVAL_STATUS_MAP[vendorApprovalStatus]
|
|
159
|
+
: undefined,
|
|
160
|
+
vendorApprovalDocsUrl: vendorApprovalDocsUrl.trim() || undefined,
|
|
148
161
|
});
|
|
149
162
|
setMode("view");
|
|
150
163
|
onUpdated?.(updated);
|
|
@@ -153,9 +166,10 @@ export function OAuthAppDetailPanel({
|
|
|
153
166
|
}
|
|
154
167
|
},
|
|
155
168
|
[
|
|
156
|
-
|
|
157
|
-
scopes, userinfoUrl, scopeParameterName,
|
|
158
|
-
vendorApprovalDocsUrl, update, clearUpdateError,
|
|
169
|
+
oauthApp, provider, clientId, clientSecret, authorizationUrl, tokenUrl,
|
|
170
|
+
scopes, userinfoUrl, scopeParameterName, tokenEndpointAuthMethod,
|
|
171
|
+
vendorApprovalStatus, vendorApprovalDocsUrl, update, clearUpdateError,
|
|
172
|
+
onUpdated,
|
|
159
173
|
],
|
|
160
174
|
);
|
|
161
175
|
|
|
@@ -372,6 +386,40 @@ export function OAuthAppDetailPanel({
|
|
|
372
386
|
disabled={isUpdating}
|
|
373
387
|
/>
|
|
374
388
|
|
|
389
|
+
<div className="stg:space-y-1">
|
|
390
|
+
<label
|
|
391
|
+
htmlFor={`${baseId}-token-auth-method`}
|
|
392
|
+
className="stg:text-xs stg:font-medium stg:text-foreground"
|
|
393
|
+
>
|
|
394
|
+
Token endpoint auth method
|
|
395
|
+
</label>
|
|
396
|
+
<select
|
|
397
|
+
id={`${baseId}-token-auth-method`}
|
|
398
|
+
value={tokenEndpointAuthMethod}
|
|
399
|
+
onChange={(e) =>
|
|
400
|
+
setTokenEndpointAuthMethod(
|
|
401
|
+
e.target.value as typeof tokenEndpointAuthMethod,
|
|
402
|
+
)
|
|
403
|
+
}
|
|
404
|
+
disabled={isUpdating}
|
|
405
|
+
className={cn(
|
|
406
|
+
"stg:w-full stg:rounded-md stg:border stg:border-input stg:bg-background stg:px-2.5 stg:py-1.5 stg:text-xs stg:text-foreground",
|
|
407
|
+
"stg:focus-visible:outline-none stg:focus-visible:ring-1 stg:focus-visible:ring-ring",
|
|
408
|
+
"stg:disabled:pointer-events-none stg:disabled:opacity-50",
|
|
409
|
+
)}
|
|
410
|
+
>
|
|
411
|
+
<option value="unspecified">
|
|
412
|
+
Unspecified (treated as HTTP Basic)
|
|
413
|
+
</option>
|
|
414
|
+
<option value="client_secret_basic">
|
|
415
|
+
client_secret_basic (HTTP Basic header)
|
|
416
|
+
</option>
|
|
417
|
+
<option value="client_secret_post">
|
|
418
|
+
client_secret_post (form body)
|
|
419
|
+
</option>
|
|
420
|
+
</select>
|
|
421
|
+
</div>
|
|
422
|
+
|
|
375
423
|
<div className="stg:space-y-1">
|
|
376
424
|
<label
|
|
377
425
|
htmlFor={`${baseId}-approval-status`}
|
|
@@ -481,6 +529,29 @@ function approvalStatusToKey(
|
|
|
481
529
|
}
|
|
482
530
|
}
|
|
483
531
|
|
|
532
|
+
const TOKEN_AUTH_METHOD_MAP = {
|
|
533
|
+
client_secret_basic: TokenEndpointAuthMethod.CLIENT_SECRET_BASIC,
|
|
534
|
+
client_secret_post: TokenEndpointAuthMethod.CLIENT_SECRET_POST,
|
|
535
|
+
} as const;
|
|
536
|
+
|
|
537
|
+
const TOKEN_AUTH_METHOD_LABELS: Record<number, string> = {
|
|
538
|
+
[TokenEndpointAuthMethod.CLIENT_SECRET_BASIC]: "client_secret_basic",
|
|
539
|
+
[TokenEndpointAuthMethod.CLIENT_SECRET_POST]: "client_secret_post",
|
|
540
|
+
};
|
|
541
|
+
|
|
542
|
+
function tokenAuthMethodToKey(
|
|
543
|
+
method?: TokenEndpointAuthMethod,
|
|
544
|
+
): "unspecified" | "client_secret_basic" | "client_secret_post" {
|
|
545
|
+
switch (method) {
|
|
546
|
+
case TokenEndpointAuthMethod.CLIENT_SECRET_BASIC:
|
|
547
|
+
return "client_secret_basic";
|
|
548
|
+
case TokenEndpointAuthMethod.CLIENT_SECRET_POST:
|
|
549
|
+
return "client_secret_post";
|
|
550
|
+
default:
|
|
551
|
+
return "unspecified";
|
|
552
|
+
}
|
|
553
|
+
}
|
|
554
|
+
|
|
484
555
|
// ---------------------------------------------------------------------------
|
|
485
556
|
// View mode
|
|
486
557
|
// ---------------------------------------------------------------------------
|
|
@@ -514,6 +585,18 @@ function ViewMode({
|
|
|
514
585
|
mono
|
|
515
586
|
/>
|
|
516
587
|
)}
|
|
588
|
+
{spec?.tokenEndpointAuthMethod !== undefined &&
|
|
589
|
+
spec.tokenEndpointAuthMethod !==
|
|
590
|
+
TokenEndpointAuthMethod.UNSPECIFIED && (
|
|
591
|
+
<Field
|
|
592
|
+
label="Token endpoint auth method"
|
|
593
|
+
value={
|
|
594
|
+
TOKEN_AUTH_METHOD_LABELS[spec.tokenEndpointAuthMethod] ??
|
|
595
|
+
"Unknown"
|
|
596
|
+
}
|
|
597
|
+
mono
|
|
598
|
+
/>
|
|
599
|
+
)}
|
|
517
600
|
{spec?.vendorApprovalStatus !== undefined &&
|
|
518
601
|
spec.vendorApprovalStatus !== VendorApprovalStatus.UNSPECIFIED && (
|
|
519
602
|
<Field
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import { describe, it, expect, vi, afterEach } from "vitest";
|
|
2
|
+
import { cleanup, fireEvent, render, screen, waitFor } from "@testing-library/react";
|
|
3
|
+
import { create } from "@bufbuild/protobuf";
|
|
4
|
+
import {
|
|
5
|
+
OAuthAppSchema,
|
|
6
|
+
type OAuthApp,
|
|
7
|
+
} from "@stigmer/protos/ai/stigmer/iam/oauthapp/v1/api_pb";
|
|
8
|
+
import { TokenEndpointAuthMethod } from "@stigmer/protos/ai/stigmer/iam/oauthapp/v1/spec_pb";
|
|
9
|
+
import type { OAuthAppInput } from "@stigmer/sdk";
|
|
10
|
+
import { StigmerContext } from "../../context";
|
|
11
|
+
import { OAuthAppDetailPanel } from "../OAuthAppDetailPanel";
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Regression suite for the full-spec-replace wipe bug: the panel must
|
|
15
|
+
* spread `toOAuthAppUpdateInput` and override only what it edits, so a
|
|
16
|
+
* spec field the form does not know about survives the save. The secret
|
|
17
|
+
* has its own contract: an empty secret input sends the fetched value —
|
|
18
|
+
* the server's redaction marker — which the update pipeline treats as
|
|
19
|
+
* "keep the stored secret" (oss#395 pins the marker/ciphertext boundary).
|
|
20
|
+
*/
|
|
21
|
+
|
|
22
|
+
const REDACTED = "***REDACTED***";
|
|
23
|
+
|
|
24
|
+
const APP: OAuthApp = create(OAuthAppSchema, {
|
|
25
|
+
metadata: {
|
|
26
|
+
id: "oa-1",
|
|
27
|
+
name: "GitHub OAuth",
|
|
28
|
+
slug: "github-oauth",
|
|
29
|
+
org: "acme",
|
|
30
|
+
},
|
|
31
|
+
spec: {
|
|
32
|
+
provider: "github",
|
|
33
|
+
clientId: "gh-client-1",
|
|
34
|
+
clientSecret: REDACTED,
|
|
35
|
+
authorizationUrl: "https://github.com/login/oauth/authorize",
|
|
36
|
+
tokenUrl: "https://github.com/login/oauth/access_token",
|
|
37
|
+
scopes: ["repo"],
|
|
38
|
+
tokenEndpointAuthMethod: TokenEndpointAuthMethod.CLIENT_SECRET_POST,
|
|
39
|
+
},
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
function renderPanel(update: ReturnType<typeof vi.fn>) {
|
|
43
|
+
const client = {
|
|
44
|
+
oauthapp: { update },
|
|
45
|
+
} as never;
|
|
46
|
+
return render(
|
|
47
|
+
<StigmerContext.Provider value={client}>
|
|
48
|
+
<OAuthAppDetailPanel oauthApp={APP} />
|
|
49
|
+
</StigmerContext.Provider>,
|
|
50
|
+
);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
afterEach(cleanup);
|
|
54
|
+
|
|
55
|
+
describe("OAuthAppDetailPanel save payload", () => {
|
|
56
|
+
it("keeps the stored secret (redaction marker) when the secret input is left empty", async () => {
|
|
57
|
+
const update = vi.fn(async (_input: OAuthAppInput) => APP);
|
|
58
|
+
renderPanel(update);
|
|
59
|
+
|
|
60
|
+
fireEvent.click(screen.getByRole("button", { name: "Edit" }));
|
|
61
|
+
const provider = await screen.findByLabelText("Provider");
|
|
62
|
+
fireEvent.change(provider, { target: { value: "github-enterprise" } });
|
|
63
|
+
fireEvent.click(screen.getByRole("button", { name: "Save changes" }));
|
|
64
|
+
|
|
65
|
+
await waitFor(() => expect(update).toHaveBeenCalledTimes(1));
|
|
66
|
+
const input = update.mock.calls[0]![0];
|
|
67
|
+
|
|
68
|
+
// Empty secret input → the redaction marker rides along → the server
|
|
69
|
+
// preserves the stored secret.
|
|
70
|
+
expect(input.clientSecret).toBe(REDACTED);
|
|
71
|
+
// The edited field.
|
|
72
|
+
expect(input.provider).toBe("github-enterprise");
|
|
73
|
+
// Unedited fields round-trip.
|
|
74
|
+
expect(input.tokenEndpointAuthMethod).toBe(
|
|
75
|
+
TokenEndpointAuthMethod.CLIENT_SECRET_POST,
|
|
76
|
+
);
|
|
77
|
+
expect(input.scopes).toEqual(["repo"]);
|
|
78
|
+
expect(input.org).toBe("acme");
|
|
79
|
+
expect(input.slug).toBe("github-oauth");
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
it("sends a newly entered secret instead of the marker", async () => {
|
|
83
|
+
const update = vi.fn(async (_input: OAuthAppInput) => APP);
|
|
84
|
+
renderPanel(update);
|
|
85
|
+
|
|
86
|
+
fireEvent.click(screen.getByRole("button", { name: "Edit" }));
|
|
87
|
+
const secret = await screen.findByLabelText("Client secret");
|
|
88
|
+
fireEvent.change(secret, { target: { value: "new-secret" } });
|
|
89
|
+
fireEvent.click(screen.getByRole("button", { name: "Save changes" }));
|
|
90
|
+
|
|
91
|
+
await waitFor(() => expect(update).toHaveBeenCalledTimes(1));
|
|
92
|
+
expect(update.mock.calls[0]![0].clientSecret).toBe("new-secret");
|
|
93
|
+
});
|
|
94
|
+
});
|