@oscharko-dev/keiko-server 0.2.7 → 0.2.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/.tsbuildinfo +1 -1
- package/dist/chat-handlers.d.ts +18 -2
- package/dist/chat-handlers.d.ts.map +1 -1
- package/dist/chat-handlers.js +185 -3
- package/dist/command-runner-errors.d.ts +17 -0
- package/dist/command-runner-errors.d.ts.map +1 -0
- package/dist/command-runner-errors.js +37 -0
- package/dist/command-runner-evidence.d.ts +23 -0
- package/dist/command-runner-evidence.d.ts.map +1 -0
- package/dist/command-runner-evidence.js +69 -0
- package/dist/command-runner-routes.d.ts +7 -0
- package/dist/command-runner-routes.d.ts.map +1 -0
- package/dist/command-runner-routes.js +175 -0
- package/dist/command-runner.d.ts +29 -0
- package/dist/command-runner.d.ts.map +1 -0
- package/dist/command-runner.js +348 -0
- package/dist/conversation-prompt.d.ts +2 -2
- package/dist/conversation-prompt.d.ts.map +1 -1
- package/dist/conversation-prompt.js +17 -1
- package/dist/csp.d.ts.map +1 -1
- package/dist/csp.js +3 -0
- package/dist/deps.d.ts +28 -1
- package/dist/deps.d.ts.map +1 -1
- package/dist/deps.js +288 -13
- package/dist/discussion-prompt.d.ts +4 -0
- package/dist/discussion-prompt.d.ts.map +1 -0
- package/dist/discussion-prompt.js +19 -0
- package/dist/editor/agentActionAudit.d.ts +18 -0
- package/dist/editor/agentActionAudit.d.ts.map +1 -0
- package/dist/editor/agentActionAudit.js +80 -0
- package/dist/editor/agentRoutes.d.ts +1 -0
- package/dist/editor/agentRoutes.d.ts.map +1 -1
- package/dist/editor/agentRoutes.js +292 -55
- package/dist/editor/agentSessionRegistry.d.ts +35 -0
- package/dist/editor/agentSessionRegistry.d.ts.map +1 -0
- package/dist/editor/agentSessionRegistry.js +243 -0
- package/dist/editor/completionRoutes.d.ts.map +1 -1
- package/dist/editor/completionRoutes.js +5 -10
- package/dist/editor/languageRoutes.d.ts +12 -1
- package/dist/editor/languageRoutes.d.ts.map +1 -1
- package/dist/editor/languageRoutes.js +71 -8
- package/dist/editor/languageService.d.ts +3 -2
- package/dist/editor/languageService.d.ts.map +1 -1
- package/dist/editor/languageService.js +41 -3
- package/dist/editor/languageServiceHost.d.ts.map +1 -1
- package/dist/editor/languageServiceHost.js +2 -2
- package/dist/editor/lsp/hostLanguageOperation.d.ts +17 -0
- package/dist/editor/lsp/hostLanguageOperation.d.ts.map +1 -0
- package/dist/editor/lsp/hostLanguageOperation.js +436 -0
- package/dist/editor/lsp/hostLanguageProviders.d.ts +26 -0
- package/dist/editor/lsp/hostLanguageProviders.d.ts.map +1 -0
- package/dist/editor/lsp/hostLanguageProviders.js +161 -0
- package/dist/editor/lsp/lspFrameCodec.d.ts +13 -0
- package/dist/editor/lsp/lspFrameCodec.d.ts.map +1 -0
- package/dist/editor/lsp/lspFrameCodec.js +164 -0
- package/dist/editor/lsp/lspJsonRpcClient.d.ts +34 -0
- package/dist/editor/lsp/lspJsonRpcClient.d.ts.map +1 -0
- package/dist/editor/lsp/lspJsonRpcClient.js +173 -0
- package/dist/editor/lsp/lspLanguageProvider.d.ts +7 -0
- package/dist/editor/lsp/lspLanguageProvider.d.ts.map +1 -0
- package/dist/editor/lsp/lspLanguageProvider.js +29 -0
- package/dist/editor/lsp/lspLifecycleLedger.d.ts +5 -0
- package/dist/editor/lsp/lspLifecycleLedger.d.ts.map +1 -0
- package/dist/editor/lsp/lspLifecycleLedger.js +37 -0
- package/dist/editor/lsp/lspNodeAdapter.d.ts +31 -0
- package/dist/editor/lsp/lspNodeAdapter.d.ts.map +1 -0
- package/dist/editor/lsp/lspNodeAdapter.js +230 -0
- package/dist/editor/lsp/lspProcessManager.d.ts +24 -0
- package/dist/editor/lsp/lspProcessManager.d.ts.map +1 -0
- package/dist/editor/lsp/lspProcessManager.js +255 -0
- package/dist/editor/lsp/lspRestartThrottle.d.ts +6 -0
- package/dist/editor/lsp/lspRestartThrottle.d.ts.map +1 -0
- package/dist/editor/lsp/lspRestartThrottle.js +24 -0
- package/dist/editor/lsp/lspStatusRoute.d.ts +8 -0
- package/dist/editor/lsp/lspStatusRoute.d.ts.map +1 -0
- package/dist/editor/lsp/lspStatusRoute.js +22 -0
- package/dist/editor/lsp/lspTransport.d.ts +19 -0
- package/dist/editor/lsp/lspTransport.d.ts.map +1 -0
- package/dist/editor/lsp/lspTransport.js +55 -0
- package/dist/editor/lsp/testing/fakeLspProcess.d.ts +23 -0
- package/dist/editor/lsp/testing/fakeLspProcess.d.ts.map +1 -0
- package/dist/editor/lsp/testing/fakeLspProcess.js +132 -0
- package/dist/files.d.ts +63 -0
- package/dist/files.d.ts.map +1 -1
- package/dist/files.js +799 -1
- package/dist/gateway-readiness.d.ts +6 -0
- package/dist/gateway-readiness.d.ts.map +1 -0
- package/dist/gateway-readiness.js +624 -0
- package/dist/gateway-setup.d.ts +2 -0
- package/dist/gateway-setup.d.ts.map +1 -1
- package/dist/gateway-setup.js +275 -11
- package/dist/gitDelivery/actionSheetProjection.d.ts +30 -0
- package/dist/gitDelivery/actionSheetProjection.d.ts.map +1 -0
- package/dist/gitDelivery/actionSheetProjection.js +206 -0
- package/dist/gitDelivery/actionSheetRoutes.d.ts +29 -0
- package/dist/gitDelivery/actionSheetRoutes.d.ts.map +1 -0
- package/dist/gitDelivery/actionSheetRoutes.js +293 -0
- package/dist/gitDelivery/agentOperationsRoutes.d.ts +33 -0
- package/dist/gitDelivery/agentOperationsRoutes.d.ts.map +1 -0
- package/dist/gitDelivery/agentOperationsRoutes.js +405 -0
- package/dist/gitDelivery/commitRoutes.d.ts +23 -0
- package/dist/gitDelivery/commitRoutes.d.ts.map +1 -0
- package/dist/gitDelivery/commitRoutes.js +204 -0
- package/dist/gitDelivery/evidenceRoutes.d.ts +9 -0
- package/dist/gitDelivery/evidenceRoutes.d.ts.map +1 -0
- package/dist/gitDelivery/evidenceRoutes.js +101 -0
- package/dist/gitDelivery/execution.d.ts +38 -0
- package/dist/gitDelivery/execution.d.ts.map +1 -0
- package/dist/gitDelivery/execution.js +117 -0
- package/dist/gitDelivery/localMutationRoutes.d.ts +30 -0
- package/dist/gitDelivery/localMutationRoutes.d.ts.map +1 -0
- package/dist/gitDelivery/localMutationRoutes.js +165 -0
- package/dist/gitDelivery/mergeExecution.d.ts +63 -0
- package/dist/gitDelivery/mergeExecution.d.ts.map +1 -0
- package/dist/gitDelivery/mergeExecution.js +168 -0
- package/dist/gitDelivery/mergeRoutes.d.ts +12 -0
- package/dist/gitDelivery/mergeRoutes.d.ts.map +1 -0
- package/dist/gitDelivery/mergeRoutes.js +218 -0
- package/dist/gitDelivery/mutationEvidenceLedger.d.ts +23 -0
- package/dist/gitDelivery/mutationEvidenceLedger.d.ts.map +1 -0
- package/dist/gitDelivery/mutationEvidenceLedger.js +87 -0
- package/dist/gitDelivery/prExecution.d.ts +54 -0
- package/dist/gitDelivery/prExecution.d.ts.map +1 -0
- package/dist/gitDelivery/prExecution.js +192 -0
- package/dist/gitDelivery/prRoutes.d.ts +12 -0
- package/dist/gitDelivery/prRoutes.d.ts.map +1 -0
- package/dist/gitDelivery/prRoutes.js +256 -0
- package/dist/gitDelivery/pushExecution.d.ts +43 -0
- package/dist/gitDelivery/pushExecution.d.ts.map +1 -0
- package/dist/gitDelivery/pushExecution.js +124 -0
- package/dist/gitDelivery/pushRoutes.d.ts +12 -0
- package/dist/gitDelivery/pushRoutes.d.ts.map +1 -0
- package/dist/gitDelivery/pushRoutes.js +200 -0
- package/dist/gitDelivery/requestGuards.d.ts +15 -0
- package/dist/gitDelivery/requestGuards.d.ts.map +1 -0
- package/dist/gitDelivery/requestGuards.js +97 -0
- package/dist/gitDelivery/syncEvidence.d.ts +37 -0
- package/dist/gitDelivery/syncEvidence.d.ts.map +1 -0
- package/dist/gitDelivery/syncEvidence.js +85 -0
- package/dist/gitDelivery/syncExecution.d.ts +30 -0
- package/dist/gitDelivery/syncExecution.d.ts.map +1 -0
- package/dist/gitDelivery/syncExecution.js +266 -0
- package/dist/gitDelivery/syncRoutes.d.ts +13 -0
- package/dist/gitDelivery/syncRoutes.d.ts.map +1 -0
- package/dist/gitDelivery/syncRoutes.js +200 -0
- package/dist/gitPorcelainStatus.d.ts +15 -0
- package/dist/gitPorcelainStatus.d.ts.map +1 -0
- package/dist/gitPorcelainStatus.js +104 -0
- package/dist/gitRepositoryReads.d.ts +10 -0
- package/dist/gitRepositoryReads.d.ts.map +1 -0
- package/dist/gitRepositoryReads.js +314 -0
- package/dist/gitRepositoryRoutes.d.ts +7 -0
- package/dist/gitRepositoryRoutes.d.ts.map +1 -0
- package/dist/gitRepositoryRoutes.js +221 -0
- package/dist/gitRoutes.d.ts +66 -0
- package/dist/gitRoutes.d.ts.map +1 -0
- package/dist/gitRoutes.js +543 -0
- package/dist/governed-workflow.d.ts +2 -0
- package/dist/governed-workflow.d.ts.map +1 -1
- package/dist/governed-workflow.js +4 -0
- package/dist/grounded-qa-hybrid.d.ts.map +1 -1
- package/dist/grounded-qa-hybrid.js +2 -0
- package/dist/grounded-qa-multi-source.d.ts.map +1 -1
- package/dist/grounded-qa-multi-source.js +1 -0
- package/dist/grounded-qa.d.ts +11 -0
- package/dist/grounded-qa.d.ts.map +1 -1
- package/dist/grounded-qa.js +14 -4
- package/dist/headers.d.ts +4 -1
- package/dist/headers.d.ts.map +1 -1
- package/dist/headers.js +11 -4
- package/dist/index.d.ts +8 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +9 -1
- package/dist/local-knowledge-grounded-qa.d.ts.map +1 -1
- package/dist/local-knowledge-grounded-qa.js +11 -2
- package/dist/qualityIntelligence/figmaSnapshotRoutes.d.ts +1 -1
- package/dist/qualityIntelligence/figmaSnapshotRoutes.d.ts.map +1 -1
- package/dist/qualityIntelligence/figmaSnapshotRoutes.js +1 -1
- package/dist/read-handlers.d.ts +5 -0
- package/dist/read-handlers.d.ts.map +1 -1
- package/dist/read-handlers.js +57 -1
- package/dist/routes.d.ts.map +1 -1
- package/dist/routes.js +260 -12
- package/dist/run-engine.d.ts.map +1 -1
- package/dist/run-engine.js +3 -0
- package/dist/run-handlers.d.ts +0 -1
- package/dist/run-handlers.d.ts.map +1 -1
- package/dist/run-handlers.js +64 -211
- package/dist/run-request.d.ts +11 -0
- package/dist/run-request.d.ts.map +1 -1
- package/dist/run-request.js +158 -10
- package/dist/runtime/capabilityDetector.d.ts +38 -0
- package/dist/runtime/capabilityDetector.d.ts.map +1 -0
- package/dist/runtime/capabilityDetector.js +443 -0
- package/dist/runtime/capabilityRoutes.d.ts +9 -0
- package/dist/runtime/capabilityRoutes.d.ts.map +1 -0
- package/dist/runtime/capabilityRoutes.js +45 -0
- package/dist/runtime/containerEngineDetector.d.ts +17 -0
- package/dist/runtime/containerEngineDetector.d.ts.map +1 -0
- package/dist/runtime/containerEngineDetector.js +222 -0
- package/dist/runtime/containerRoutes.d.ts +8 -0
- package/dist/runtime/containerRoutes.d.ts.map +1 -0
- package/dist/runtime/containerRoutes.js +207 -0
- package/dist/runtime/containerRunner-errors.d.ts +18 -0
- package/dist/runtime/containerRunner-errors.d.ts.map +1 -0
- package/dist/runtime/containerRunner-errors.js +42 -0
- package/dist/runtime/containerRunner-evidence.d.ts +24 -0
- package/dist/runtime/containerRunner-evidence.d.ts.map +1 -0
- package/dist/runtime/containerRunner-evidence.js +74 -0
- package/dist/runtime/containerRunner.d.ts +37 -0
- package/dist/runtime/containerRunner.d.ts.map +1 -0
- package/dist/runtime/containerRunner.js +443 -0
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +24 -4
- package/dist/store/db.d.ts.map +1 -1
- package/dist/store/db.js +2 -1
- package/dist/store/index.d.ts +1 -1
- package/dist/store/index.d.ts.map +1 -1
- package/dist/store/messages.d.ts +2 -1
- package/dist/store/messages.d.ts.map +1 -1
- package/dist/store/messages.js +46 -4
- package/dist/store/schema.d.ts +1 -1
- package/dist/store/schema.d.ts.map +1 -1
- package/dist/store/schema.js +68 -1
- package/dist/store/types.d.ts +3 -2
- package/dist/store/types.d.ts.map +1 -1
- package/dist/task-workspace/active-store.d.ts +21 -0
- package/dist/task-workspace/active-store.d.ts.map +1 -0
- package/dist/task-workspace/active-store.js +55 -0
- package/dist/task-workspace/authorization.d.ts +7 -0
- package/dist/task-workspace/authorization.d.ts.map +1 -0
- package/dist/task-workspace/authorization.js +54 -0
- package/dist/task-workspace/binding.d.ts +3 -0
- package/dist/task-workspace/binding.d.ts.map +1 -0
- package/dist/task-workspace/binding.js +22 -0
- package/dist/task-workspace/cleanup.d.ts +4 -0
- package/dist/task-workspace/cleanup.d.ts.map +1 -0
- package/dist/task-workspace/cleanup.js +428 -0
- package/dist/task-workspace/errors.d.ts +14 -0
- package/dist/task-workspace/errors.d.ts.map +1 -0
- package/dist/task-workspace/errors.js +81 -0
- package/dist/task-workspace/evidence.d.ts +32 -0
- package/dist/task-workspace/evidence.d.ts.map +1 -0
- package/dist/task-workspace/evidence.js +52 -0
- package/dist/task-workspace/field-safety.d.ts +3 -0
- package/dist/task-workspace/field-safety.d.ts.map +1 -0
- package/dist/task-workspace/field-safety.js +42 -0
- package/dist/task-workspace/health.d.ts +4 -0
- package/dist/task-workspace/health.d.ts.map +1 -0
- package/dist/task-workspace/health.js +163 -0
- package/dist/task-workspace/lifecycle.d.ts +3 -0
- package/dist/task-workspace/lifecycle.d.ts.map +1 -0
- package/dist/task-workspace/lifecycle.js +248 -0
- package/dist/task-workspace/locks.d.ts +13 -0
- package/dist/task-workspace/locks.d.ts.map +1 -0
- package/dist/task-workspace/locks.js +44 -0
- package/dist/task-workspace/managed-root.d.ts +7 -0
- package/dist/task-workspace/managed-root.d.ts.map +1 -0
- package/dist/task-workspace/managed-root.js +98 -0
- package/dist/task-workspace/mutex.d.ts +8 -0
- package/dist/task-workspace/mutex.d.ts.map +1 -0
- package/dist/task-workspace/mutex.js +82 -0
- package/dist/task-workspace/naming.d.ts +15 -0
- package/dist/task-workspace/naming.d.ts.map +1 -0
- package/dist/task-workspace/naming.js +0 -0
- package/dist/task-workspace/provisioning.d.ts +3 -0
- package/dist/task-workspace/provisioning.d.ts.map +1 -0
- package/dist/task-workspace/provisioning.js +528 -0
- package/dist/task-workspace/reconciliation.d.ts +15 -0
- package/dist/task-workspace/reconciliation.d.ts.map +1 -0
- package/dist/task-workspace/reconciliation.js +274 -0
- package/dist/task-workspace/repair.d.ts +3 -0
- package/dist/task-workspace/repair.d.ts.map +1 -0
- package/dist/task-workspace/repair.js +286 -0
- package/dist/task-workspace/routes.d.ts +19 -0
- package/dist/task-workspace/routes.d.ts.map +1 -0
- package/dist/task-workspace/routes.js +481 -0
- package/dist/task-workspace/store.d.ts +12 -0
- package/dist/task-workspace/store.d.ts.map +1 -0
- package/dist/task-workspace/store.js +128 -0
- package/dist/task-workspace/types.d.ts +170 -0
- package/dist/task-workspace/types.d.ts.map +1 -0
- package/dist/task-workspace/types.js +5 -0
- package/dist/voice-action-governance.d.ts +23 -0
- package/dist/voice-action-governance.d.ts.map +1 -0
- package/dist/voice-action-governance.js +126 -0
- package/dist/voice-handlers.d.ts +6 -0
- package/dist/voice-handlers.d.ts.map +1 -0
- package/dist/voice-handlers.js +570 -0
- package/dist/voice-realtime-grounded-tool.d.ts +31 -0
- package/dist/voice-realtime-grounded-tool.d.ts.map +1 -0
- package/dist/voice-realtime-grounded-tool.js +322 -0
- package/dist/voice-realtime.d.ts +69 -0
- package/dist/voice-realtime.d.ts.map +1 -0
- package/dist/voice-realtime.js +787 -0
- package/dist/workspace-state-handlers.d.ts +5 -0
- package/dist/workspace-state-handlers.d.ts.map +1 -0
- package/dist/workspace-state-handlers.js +106 -0
- package/package.json +20 -19
- package/dist/grounded-handoff.d.ts +0 -4
- package/dist/grounded-handoff.d.ts.map +0 -1
- package/dist/grounded-handoff.js +0 -445
package/dist/gateway-setup.js
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
// chat-completions smoke call, stores the resulting config on disk with private permissions, and
|
|
4
4
|
// updates the in-memory runtime config without exposing credentials back to the browser.
|
|
5
5
|
import { resolveEvidenceDir } from "@oscharko-dev/keiko-evidence";
|
|
6
|
-
import { apiKeyHeaderValue, ConfigInvalidError, DEFAULT_API_KEY_HEADER_NAME, Gateway, createDefaultChatCapability, listConfiguredCapabilities, normalizeApiKeyHeaderName, parseGatewayConfig, toSafeObject, validateBaseUrl, } from "@oscharko-dev/keiko-model-gateway";
|
|
6
|
+
import { apiKeyHeaderValue, ConfigInvalidError, DEFAULT_API_KEY_HEADER_NAME, Gateway, createDefaultChatCapability, listConfiguredCapabilities, modelSupportsSpeechInput, normalizeApiKeyHeaderName, parseGatewayConfig, selectSpeechToTextModel, toSafeObject, validateBaseUrl, VOICE_PROVIDER_LOCALITIES, } from "@oscharko-dev/keiko-model-gateway";
|
|
7
7
|
import { gatewayFetch, readJsonCapped } from "@oscharko-dev/keiko-model-gateway/internal/http";
|
|
8
8
|
import { redact } from "@oscharko-dev/keiko-security";
|
|
9
9
|
import { errorBody } from "./routes.js";
|
|
@@ -108,10 +108,25 @@ function isAzureFoundryBaseUrl(baseUrl) {
|
|
|
108
108
|
return false;
|
|
109
109
|
}
|
|
110
110
|
}
|
|
111
|
+
function createDefaultSetupCapability(modelId, embeddingModelIds) {
|
|
112
|
+
if (embeddingModelIds?.includes(modelId) === true) {
|
|
113
|
+
return createDefaultEmbeddingCapabilityForSetup(modelId);
|
|
114
|
+
}
|
|
115
|
+
const capability = createDefaultChatCapability(modelId);
|
|
116
|
+
if (modelId.toLowerCase().includes("mistral")) {
|
|
117
|
+
return {
|
|
118
|
+
...capability,
|
|
119
|
+
toolCalling: false,
|
|
120
|
+
knownLimitations: [
|
|
121
|
+
...capability.knownLimitations,
|
|
122
|
+
"Tool calling is disabled by default for Mistral deployments until endpoint readiness verifies it",
|
|
123
|
+
],
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
return capability;
|
|
127
|
+
}
|
|
111
128
|
function providerRaw(modelId, baseUrl, apiKey, options = {}) {
|
|
112
|
-
const defaultCapability = options.embeddingModelIds
|
|
113
|
-
? createDefaultEmbeddingCapabilityForSetup(modelId)
|
|
114
|
-
: createDefaultChatCapability(modelId);
|
|
129
|
+
const defaultCapability = createDefaultSetupCapability(modelId, options.embeddingModelIds);
|
|
115
130
|
return {
|
|
116
131
|
modelId,
|
|
117
132
|
baseUrl,
|
|
@@ -125,6 +140,41 @@ function providerRaw(modelId, baseUrl, apiKey, options = {}) {
|
|
|
125
140
|
retryBaseDelayMs: options.retryBaseDelayMs ?? 500,
|
|
126
141
|
};
|
|
127
142
|
}
|
|
143
|
+
function createDefaultVoiceDictationCapabilityForSetup(modelId, providerLocality) {
|
|
144
|
+
return {
|
|
145
|
+
id: modelId,
|
|
146
|
+
kind: "voice",
|
|
147
|
+
contextWindow: 0,
|
|
148
|
+
maxOutputTokens: 0,
|
|
149
|
+
toolCalling: false,
|
|
150
|
+
structuredOutput: false,
|
|
151
|
+
streaming: false,
|
|
152
|
+
supportsImageInput: false,
|
|
153
|
+
supportsDocumentInput: false,
|
|
154
|
+
workflowEligible: false,
|
|
155
|
+
supportsSpeechInput: true,
|
|
156
|
+
voiceProviderLocality: providerLocality,
|
|
157
|
+
costClass: "low",
|
|
158
|
+
latencyClass: "fast",
|
|
159
|
+
throughputHint: "runtime-configured speech-to-text endpoint",
|
|
160
|
+
preferredUseCases: ["Dictation"],
|
|
161
|
+
knownLimitations: [
|
|
162
|
+
"Speech-to-text only; dictation is verified when the first audio clip is transcribed",
|
|
163
|
+
],
|
|
164
|
+
};
|
|
165
|
+
}
|
|
166
|
+
function voiceProviderRaw(modelId, baseUrl, apiKey, options = {}) {
|
|
167
|
+
return {
|
|
168
|
+
modelId,
|
|
169
|
+
baseUrl,
|
|
170
|
+
apiKey,
|
|
171
|
+
apiKeyHeaderName: options.apiKeyHeaderName ?? DEFAULT_API_KEY_HEADER_NAME,
|
|
172
|
+
capability: createDefaultVoiceDictationCapabilityForSetup(modelId, options.providerLocality ?? "azure-foundry"),
|
|
173
|
+
timeoutMs: options.timeoutMs ?? 30_000,
|
|
174
|
+
maxRetries: 1,
|
|
175
|
+
retryBaseDelayMs: 500,
|
|
176
|
+
};
|
|
177
|
+
}
|
|
128
178
|
function isLikelyEmbeddingModelId(modelId) {
|
|
129
179
|
return EMBEDDING_ID_PATTERN.test(modelId);
|
|
130
180
|
}
|
|
@@ -223,7 +273,19 @@ function currentImageInputModelIds(config) {
|
|
|
223
273
|
?.filter((capability) => capability.kind === "chat" && capability.supportsImageInput)
|
|
224
274
|
.map((capability) => capability.id) ?? []);
|
|
225
275
|
}
|
|
226
|
-
|
|
276
|
+
// `supportedVoicePersonas` is DERIVED at parse time from a provider's `voiceProfiles` (Issue #1557,
|
|
277
|
+
// ADR-0094 D2 / HAZARD-3). It must NOT be persisted: the strict top-level `capabilities` parser
|
|
278
|
+
// rejects it as an unrecognised input key, and re-deriving it on reload keeps a single source of
|
|
279
|
+
// truth (the credential-tier `voiceProfiles`). Strip it so a save → reload round-trip re-derives.
|
|
280
|
+
function stripDerivedVoicePersonas(capability) {
|
|
281
|
+
const { supportedVoicePersonas, ...rest } = capability;
|
|
282
|
+
return supportedVoicePersonas === undefined ? capability : rest;
|
|
283
|
+
}
|
|
284
|
+
// Exported as a test seam (mirroring `smokeTestCandidates` / the discovery-normalization exports):
|
|
285
|
+
// the preserve-existing save path round-trips a parsed config back to raw for persistence, and the
|
|
286
|
+
// Issue #1557 voice-persona round-trip (voiceProfiles preserved, derived supportedVoicePersonas
|
|
287
|
+
// stripped and re-derived on reload — ADR-0094 D2) is pinned directly against this function.
|
|
288
|
+
export function rawConfigFromCurrent(config, figmaAccessToken, timeoutMs) {
|
|
227
289
|
return {
|
|
228
290
|
providers: config.providers.map((provider) => {
|
|
229
291
|
const capability = config.capabilities?.find((item) => item.id === provider.modelId);
|
|
@@ -235,15 +297,67 @@ function rawConfigFromCurrent(config, figmaAccessToken, timeoutMs) {
|
|
|
235
297
|
timeoutMs: timeoutMs ?? provider.timeoutMs,
|
|
236
298
|
maxRetries: provider.maxRetries,
|
|
237
299
|
retryBaseDelayMs: provider.retryBaseDelayMs,
|
|
238
|
-
|
|
300
|
+
// Persist the credential-tier persona → voice-id mapping so personas survive a save; the
|
|
301
|
+
// derived content-free `supportedVoicePersonas` is stripped and re-derived on reload.
|
|
302
|
+
...(provider.voiceProfiles === undefined ? {} : { voiceProfiles: provider.voiceProfiles }),
|
|
303
|
+
...(capability === undefined ? {} : { capability: stripDerivedVoicePersonas(capability) }),
|
|
239
304
|
};
|
|
240
305
|
}),
|
|
241
306
|
circuitBreaker: config.circuitBreaker,
|
|
242
|
-
...(config.capabilities === undefined
|
|
307
|
+
...(config.capabilities === undefined
|
|
308
|
+
? {}
|
|
309
|
+
: { capabilities: config.capabilities.map(stripDerivedVoicePersonas) }),
|
|
243
310
|
...(config.grounding === undefined ? {} : { grounding: config.grounding }),
|
|
244
311
|
...(figmaAccessToken === undefined ? {} : { figma: { accessToken: figmaAccessToken } }),
|
|
245
312
|
};
|
|
246
313
|
}
|
|
314
|
+
function rawCapabilityIsSpeechInputVoice(value) {
|
|
315
|
+
return (isRecord(value) &&
|
|
316
|
+
value.kind === "voice" &&
|
|
317
|
+
(value.supportsSpeechInput === true || value.supportsRealtimeVoice === true));
|
|
318
|
+
}
|
|
319
|
+
function rawProviderIsSpeechInputVoice(value) {
|
|
320
|
+
return isRecord(value) && rawCapabilityIsSpeechInputVoice(value.capability);
|
|
321
|
+
}
|
|
322
|
+
function setupVoiceProviderFromCurrent(current) {
|
|
323
|
+
const provider = currentSpeechInputProvider(current);
|
|
324
|
+
if (provider === undefined) {
|
|
325
|
+
return undefined;
|
|
326
|
+
}
|
|
327
|
+
const capability = currentSpeechInputCapability(current, provider.modelId);
|
|
328
|
+
return {
|
|
329
|
+
modelId: provider.modelId,
|
|
330
|
+
baseUrl: provider.baseUrl,
|
|
331
|
+
apiKey: provider.apiKey,
|
|
332
|
+
apiKeyHeaderName: provider.apiKeyHeaderName ?? DEFAULT_API_KEY_HEADER_NAME,
|
|
333
|
+
timeoutMs: provider.timeoutMs,
|
|
334
|
+
providerLocality: capability?.voiceProviderLocality ?? "azure-foundry",
|
|
335
|
+
};
|
|
336
|
+
}
|
|
337
|
+
function applyVoiceProvider(rawConfig, voiceProvider) {
|
|
338
|
+
if (voiceProvider === undefined) {
|
|
339
|
+
return rawConfig;
|
|
340
|
+
}
|
|
341
|
+
const providers = Array.isArray(rawConfig.providers) ? rawConfig.providers : [];
|
|
342
|
+
const nextProviders = providers.filter((provider) => !rawProviderIsSpeechInputVoice(provider) &&
|
|
343
|
+
(!isRecord(provider) || provider.modelId !== voiceProvider.modelId));
|
|
344
|
+
const nextConfig = {
|
|
345
|
+
...rawConfig,
|
|
346
|
+
providers: [
|
|
347
|
+
...nextProviders,
|
|
348
|
+
voiceProviderRaw(voiceProvider.modelId, voiceProvider.baseUrl, voiceProvider.apiKey, {
|
|
349
|
+
apiKeyHeaderName: voiceProvider.apiKeyHeaderName,
|
|
350
|
+
timeoutMs: voiceProvider.timeoutMs,
|
|
351
|
+
providerLocality: voiceProvider.providerLocality,
|
|
352
|
+
}),
|
|
353
|
+
],
|
|
354
|
+
};
|
|
355
|
+
if (Array.isArray(rawConfig.capabilities)) {
|
|
356
|
+
nextConfig.capabilities = rawConfig.capabilities.filter((capability) => !rawCapabilityIsSpeechInputVoice(capability) &&
|
|
357
|
+
(!isRecord(capability) || capability.id !== voiceProvider.modelId));
|
|
358
|
+
}
|
|
359
|
+
return nextConfig;
|
|
360
|
+
}
|
|
247
361
|
function withInheritedEgress(rawConfig, egress) {
|
|
248
362
|
if (egress === undefined || Object.hasOwn(rawConfig, "egress")) {
|
|
249
363
|
return rawConfig;
|
|
@@ -640,6 +754,19 @@ function optionalSetupPositiveInt(value, path) {
|
|
|
640
754
|
}
|
|
641
755
|
return value;
|
|
642
756
|
}
|
|
757
|
+
function parseVoiceProviderLocality(value, fallback) {
|
|
758
|
+
if (value === undefined) {
|
|
759
|
+
return fallback;
|
|
760
|
+
}
|
|
761
|
+
if (typeof value !== "string" ||
|
|
762
|
+
!VOICE_PROVIDER_LOCALITIES.includes(value)) {
|
|
763
|
+
return {
|
|
764
|
+
status: 400,
|
|
765
|
+
body: errorBody("BAD_REQUEST", "voiceProviderLocality is not supported."),
|
|
766
|
+
};
|
|
767
|
+
}
|
|
768
|
+
return value;
|
|
769
|
+
}
|
|
643
770
|
function hasNonBlankStringField(raw, key) {
|
|
644
771
|
const value = raw[key];
|
|
645
772
|
return typeof value === "string" && value.trim().length > 0;
|
|
@@ -651,12 +778,36 @@ function hasNonEmptyListField(raw, key) {
|
|
|
651
778
|
}
|
|
652
779
|
return Array.isArray(value) && value.some((item) => typeof item === "string" && item.trim());
|
|
653
780
|
}
|
|
781
|
+
function hasVoiceProviderInput(raw) {
|
|
782
|
+
return (hasNonBlankStringField(raw, "voiceBaseUrl") ||
|
|
783
|
+
hasNonBlankStringField(raw, "voiceApiKey") ||
|
|
784
|
+
hasNonBlankStringField(raw, "voiceApiKeyHeaderName") ||
|
|
785
|
+
hasNonBlankStringField(raw, "voiceModelId") ||
|
|
786
|
+
hasNonBlankStringField(raw, "voiceProviderLocality") ||
|
|
787
|
+
raw.voiceTimeoutMs !== undefined);
|
|
788
|
+
}
|
|
654
789
|
function shouldPreserveExisting(raw, current) {
|
|
655
790
|
return raw.preserveExisting === true && current !== undefined;
|
|
656
791
|
}
|
|
657
792
|
function firstProvider(current) {
|
|
658
793
|
return current?.providers[0];
|
|
659
794
|
}
|
|
795
|
+
function currentSpeechInputProvider(current) {
|
|
796
|
+
if (current === undefined) {
|
|
797
|
+
return undefined;
|
|
798
|
+
}
|
|
799
|
+
const modelId = selectSpeechToTextModel(current);
|
|
800
|
+
if (modelId === undefined) {
|
|
801
|
+
return undefined;
|
|
802
|
+
}
|
|
803
|
+
return current.providers.find((provider) => provider.modelId === modelId);
|
|
804
|
+
}
|
|
805
|
+
function currentSpeechInputCapability(current, modelId) {
|
|
806
|
+
if (current === undefined || modelId === undefined) {
|
|
807
|
+
return undefined;
|
|
808
|
+
}
|
|
809
|
+
return current.capabilities?.find((capability) => capability.id === modelId && modelSupportsSpeechInput(capability));
|
|
810
|
+
}
|
|
660
811
|
function trimmedSubmittedString(raw, key) {
|
|
661
812
|
const value = raw[key];
|
|
662
813
|
if (typeof value !== "string")
|
|
@@ -673,6 +824,12 @@ function setupApiKeyHeaderSource(raw, provider, preserveExisting) {
|
|
|
673
824
|
}
|
|
674
825
|
return provider?.apiKeyHeaderName ?? DEFAULT_API_KEY_HEADER_NAME;
|
|
675
826
|
}
|
|
827
|
+
function setupVoiceApiKeyHeaderSource(raw, provider, preserveExisting) {
|
|
828
|
+
if (raw.voiceApiKeyHeaderName !== undefined || !preserveExisting) {
|
|
829
|
+
return raw.voiceApiKeyHeaderName;
|
|
830
|
+
}
|
|
831
|
+
return provider?.apiKeyHeaderName ?? DEFAULT_API_KEY_HEADER_NAME;
|
|
832
|
+
}
|
|
676
833
|
function readSetupGatewayCredentials(raw, env, current, preserveExisting) {
|
|
677
834
|
const provider = firstProvider(current);
|
|
678
835
|
const baseUrl = submittedOrInheritedString(raw, "baseUrl", provider?.baseUrl, preserveExisting);
|
|
@@ -691,6 +848,93 @@ function readSetupGatewayCredentials(raw, env, current, preserveExisting) {
|
|
|
691
848
|
}
|
|
692
849
|
return { baseUrl, apiKey, apiKeyHeaderName };
|
|
693
850
|
}
|
|
851
|
+
function validateVoiceProviderConnection(provider, env) {
|
|
852
|
+
try {
|
|
853
|
+
parseGatewayConfig({
|
|
854
|
+
providers: [
|
|
855
|
+
voiceProviderRaw(provider.modelId, provider.baseUrl, provider.apiKey, {
|
|
856
|
+
apiKeyHeaderName: provider.apiKeyHeaderName,
|
|
857
|
+
timeoutMs: provider.timeoutMs,
|
|
858
|
+
providerLocality: provider.providerLocality,
|
|
859
|
+
}),
|
|
860
|
+
],
|
|
861
|
+
circuitBreaker: { failureThreshold: 5, cooldownMs: 30_000, halfOpenProbes: 2 },
|
|
862
|
+
}, env);
|
|
863
|
+
return undefined;
|
|
864
|
+
}
|
|
865
|
+
catch (error) {
|
|
866
|
+
if (error instanceof ConfigInvalidError) {
|
|
867
|
+
return { status: 400, body: errorBody("BAD_REQUEST", error.message) };
|
|
868
|
+
}
|
|
869
|
+
throw error;
|
|
870
|
+
}
|
|
871
|
+
}
|
|
872
|
+
function setupVoiceModelId(raw, existing, preserveExisting) {
|
|
873
|
+
const modelId = trimmedSubmittedString(raw, "voiceModelId") ??
|
|
874
|
+
(preserveExisting ? existing?.modelId : undefined) ??
|
|
875
|
+
"keiko-stt";
|
|
876
|
+
if (!isUsableModelId(modelId)) {
|
|
877
|
+
return { status: 400, body: errorBody("BAD_REQUEST", "voiceModelId is invalid.") };
|
|
878
|
+
}
|
|
879
|
+
return modelId;
|
|
880
|
+
}
|
|
881
|
+
function setupVoiceConnection(raw, existing, preserveExisting) {
|
|
882
|
+
const baseUrl = submittedOrInheritedString(raw, "voiceBaseUrl", existing?.baseUrl, preserveExisting);
|
|
883
|
+
const apiKey = submittedOrInheritedString(raw, "voiceApiKey", existing?.apiKey, preserveExisting);
|
|
884
|
+
if (baseUrl.length === 0 || apiKey.length === 0) {
|
|
885
|
+
return {
|
|
886
|
+
status: 400,
|
|
887
|
+
body: errorBody("BAD_REQUEST", "voiceBaseUrl and voiceApiKey are required for dictation."),
|
|
888
|
+
};
|
|
889
|
+
}
|
|
890
|
+
return { baseUrl, apiKey };
|
|
891
|
+
}
|
|
892
|
+
function setupVoiceApiKeyHeaderName(raw, existing, preserveExisting) {
|
|
893
|
+
return normalizeSetupApiKeyHeaderName(setupVoiceApiKeyHeaderSource(raw, existing, preserveExisting));
|
|
894
|
+
}
|
|
895
|
+
function setupVoiceProviderLocality(raw, existingCapability) {
|
|
896
|
+
return parseVoiceProviderLocality(raw.voiceProviderLocality, existingCapability?.voiceProviderLocality ?? "azure-foundry");
|
|
897
|
+
}
|
|
898
|
+
function firstRouteResult(values) {
|
|
899
|
+
return values.find(isRouteResult);
|
|
900
|
+
}
|
|
901
|
+
function readSetupVoiceProvider(raw, env, current, preserveExisting) {
|
|
902
|
+
if (!hasVoiceProviderInput(raw)) {
|
|
903
|
+
return undefined;
|
|
904
|
+
}
|
|
905
|
+
const existing = preserveExisting ? currentSpeechInputProvider(current) : undefined;
|
|
906
|
+
const existingCapability = currentSpeechInputCapability(current, existing?.modelId);
|
|
907
|
+
const modelId = setupVoiceModelId(raw, existing, preserveExisting);
|
|
908
|
+
const connection = setupVoiceConnection(raw, existing, preserveExisting);
|
|
909
|
+
const apiKeyHeaderName = setupVoiceApiKeyHeaderName(raw, existing, preserveExisting);
|
|
910
|
+
const timeoutMs = optionalSetupPositiveInt(raw.voiceTimeoutMs, "voiceTimeoutMs");
|
|
911
|
+
const providerLocality = setupVoiceProviderLocality(raw, existingCapability);
|
|
912
|
+
const routeError = firstRouteResult([
|
|
913
|
+
modelId,
|
|
914
|
+
connection,
|
|
915
|
+
apiKeyHeaderName,
|
|
916
|
+
timeoutMs,
|
|
917
|
+
providerLocality,
|
|
918
|
+
]);
|
|
919
|
+
if (routeError !== undefined) {
|
|
920
|
+
return routeError;
|
|
921
|
+
}
|
|
922
|
+
const resolvedConnection = connection;
|
|
923
|
+
const resolvedTimeoutMs = timeoutMs;
|
|
924
|
+
const provider = {
|
|
925
|
+
modelId: modelId,
|
|
926
|
+
baseUrl: resolvedConnection.baseUrl,
|
|
927
|
+
apiKey: resolvedConnection.apiKey,
|
|
928
|
+
apiKeyHeaderName: apiKeyHeaderName,
|
|
929
|
+
timeoutMs: resolvedTimeoutMs ?? existing?.timeoutMs,
|
|
930
|
+
providerLocality: providerLocality,
|
|
931
|
+
};
|
|
932
|
+
const invalidConnection = validateVoiceProviderConnection(provider, env);
|
|
933
|
+
if (invalidConnection !== undefined) {
|
|
934
|
+
return invalidConnection;
|
|
935
|
+
}
|
|
936
|
+
return provider;
|
|
937
|
+
}
|
|
694
938
|
function resolveSetupModelLists(modelLists, current, preserveExisting) {
|
|
695
939
|
const existing = preserveExisting ? current : undefined;
|
|
696
940
|
return {
|
|
@@ -731,12 +975,17 @@ function readSetupRequest(raw, env, current) {
|
|
|
731
975
|
if (isRouteResult(figmaAccessToken)) {
|
|
732
976
|
return figmaAccessToken;
|
|
733
977
|
}
|
|
978
|
+
const voiceProvider = readSetupVoiceProvider(raw, env, current, preserveExisting);
|
|
979
|
+
if (isRouteResult(voiceProvider)) {
|
|
980
|
+
return voiceProvider;
|
|
981
|
+
}
|
|
734
982
|
const resolvedModelLists = resolveSetupModelLists(modelLists, current, preserveExisting);
|
|
735
983
|
return {
|
|
736
984
|
...credentials,
|
|
737
985
|
timeoutMs,
|
|
738
986
|
deploymentNames: resolvedModelLists.deploymentNames,
|
|
739
987
|
imageInputModelIds: resolvedModelLists.imageInputModelIds,
|
|
988
|
+
voiceProvider,
|
|
740
989
|
figmaAccessToken: figmaAccessToken ?? current?.figma?.accessToken,
|
|
741
990
|
verifyGateway: setupRequiresGatewayVerification(raw, preserveExisting),
|
|
742
991
|
verifyFigmaCredential: figmaAccessToken !== undefined,
|
|
@@ -832,13 +1081,14 @@ function finalRawConfigForSetup(input, testedModelIds, embeddingModelIds, imageI
|
|
|
832
1081
|
embeddingModelIds,
|
|
833
1082
|
timeoutMs: input.timeoutMs,
|
|
834
1083
|
});
|
|
835
|
-
|
|
1084
|
+
const rawConfigWithOptionalBlocks = {
|
|
836
1085
|
...rawConfig,
|
|
837
1086
|
...(input.current?.grounding === undefined ? {} : { grounding: input.current.grounding }),
|
|
838
1087
|
...(input.figmaAccessToken === undefined
|
|
839
1088
|
? {}
|
|
840
1089
|
: { figma: { accessToken: input.figmaAccessToken } }),
|
|
841
1090
|
};
|
|
1091
|
+
return applyVoiceProvider(rawConfigWithOptionalBlocks, input.voiceProvider ?? setupVoiceProviderFromCurrent(input.current));
|
|
842
1092
|
}
|
|
843
1093
|
function skippedModelIdsForSetup(candidateModelIds, testedModelIds, embeddingModelIds) {
|
|
844
1094
|
const acceptedModelIds = new Set([...testedModelIds, ...embeddingModelIds]);
|
|
@@ -922,7 +1172,13 @@ function figmaCredentialFailureResult(error, request) {
|
|
|
922
1172
|
}
|
|
923
1173
|
return {
|
|
924
1174
|
status: 502,
|
|
925
|
-
body: errorBody("FIGMA_EGRESS_FAILED", safeError(error, [
|
|
1175
|
+
body: errorBody("FIGMA_EGRESS_FAILED", safeError(error, [
|
|
1176
|
+
request.figmaAccessToken,
|
|
1177
|
+
request.apiKey,
|
|
1178
|
+
request.baseUrl,
|
|
1179
|
+
request.voiceProvider?.apiKey,
|
|
1180
|
+
request.voiceProvider?.baseUrl,
|
|
1181
|
+
])),
|
|
926
1182
|
};
|
|
927
1183
|
}
|
|
928
1184
|
async function verifySubmittedFigmaCredential(request, deps) {
|
|
@@ -979,6 +1235,7 @@ async function trySetupCandidate(baseUrl, request, deps, gatewayConfig, tester,
|
|
|
979
1235
|
timeoutMs: request.timeoutMs,
|
|
980
1236
|
deploymentNames: request.deploymentNames,
|
|
981
1237
|
imageInputModelIds: request.imageInputModelIds,
|
|
1238
|
+
voiceProvider: request.voiceProvider,
|
|
982
1239
|
tester,
|
|
983
1240
|
discovery,
|
|
984
1241
|
env: deps.env,
|
|
@@ -991,10 +1248,17 @@ async function trySetupCandidate(baseUrl, request, deps, gatewayConfig, tester,
|
|
|
991
1248
|
return setupSuccessResult(verified.config, verified.testedModelIds, verified.skippedModelIds);
|
|
992
1249
|
}
|
|
993
1250
|
function setupCandidateError(error, request, baseUrl) {
|
|
994
|
-
return safeError(error, [
|
|
1251
|
+
return safeError(error, [
|
|
1252
|
+
request.apiKey,
|
|
1253
|
+
request.baseUrl,
|
|
1254
|
+
baseUrl,
|
|
1255
|
+
request.figmaAccessToken,
|
|
1256
|
+
request.voiceProvider?.apiKey,
|
|
1257
|
+
request.voiceProvider?.baseUrl,
|
|
1258
|
+
]);
|
|
995
1259
|
}
|
|
996
1260
|
function saveExistingConfigUpdate(request, current, deps, gatewayConfig) {
|
|
997
|
-
const rawConfig = rawConfigFromCurrent(current, request.figmaAccessToken, request.timeoutMs);
|
|
1261
|
+
const rawConfig = applyVoiceProvider(rawConfigFromCurrent(current, request.figmaAccessToken, request.timeoutMs), request.voiceProvider);
|
|
998
1262
|
const config = parseGatewayConfig(withInheritedEgress(rawConfig, currentGatewayEgressConfig(deps)), deps.env);
|
|
999
1263
|
persistGatewayConfig(rawConfig, gatewayConfig.storagePath, deps);
|
|
1000
1264
|
gatewayConfig.set(config, true);
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { type GitDeliveryActionSheet, type GitDeliveryApprovalRequirement, type GitDeliveryBranchProtection, type GitDeliveryChecksState, type GitDeliveryMergeReadiness, type GitDeliveryOrgPolicyPack, type GitDeliveryProviderCapability, type GitDeliveryPullRequestState, type GitDeliveryRemediationClass, type GitDeliveryRepoPolicyPack, type GitDeliveryResolvedInputs } from "@oscharko-dev/keiko-contracts";
|
|
2
|
+
import { type GitWorktreeSnapshot } from "@oscharko-dev/keiko-tools";
|
|
3
|
+
export interface GitDeliveryProviderStateFacts {
|
|
4
|
+
readonly pullRequest?: GitDeliveryPullRequestState | undefined;
|
|
5
|
+
readonly mergeReadiness?: GitDeliveryMergeReadiness | undefined;
|
|
6
|
+
readonly branchProtection?: GitDeliveryBranchProtection | undefined;
|
|
7
|
+
readonly checks?: GitDeliveryChecksState | undefined;
|
|
8
|
+
}
|
|
9
|
+
export interface GitDeliveryTrustedPolicyPacks {
|
|
10
|
+
readonly orgPack?: GitDeliveryOrgPolicyPack | undefined;
|
|
11
|
+
readonly repoPack?: GitDeliveryRepoPolicyPack | undefined;
|
|
12
|
+
}
|
|
13
|
+
export interface BuildActionSheetFacts {
|
|
14
|
+
readonly actionId: string;
|
|
15
|
+
readonly resolvedInputs: GitDeliveryResolvedInputs;
|
|
16
|
+
readonly worktreeSnapshot: GitWorktreeSnapshot;
|
|
17
|
+
readonly approvalRequirement: GitDeliveryApprovalRequirement;
|
|
18
|
+
readonly policyPacks: GitDeliveryTrustedPolicyPacks;
|
|
19
|
+
readonly activeProviderCapabilities: readonly GitDeliveryProviderCapability[];
|
|
20
|
+
readonly providerState: GitDeliveryProviderStateFacts;
|
|
21
|
+
readonly providerReady: boolean;
|
|
22
|
+
readonly nowMs: number;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* The pure projection. Runs preflight + policy over the supplied content-free facts and assembles the
|
|
26
|
+
* UI-safe action sheet. Deterministic: identical facts always yield an identical sheet.
|
|
27
|
+
*/
|
|
28
|
+
export declare function buildActionSheetFromFacts(facts: BuildActionSheetFacts): GitDeliveryActionSheet;
|
|
29
|
+
export type { GitDeliveryRemediationClass };
|
|
30
|
+
//# sourceMappingURL=actionSheetProjection.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"actionSheetProjection.d.ts","sourceRoot":"","sources":["../../src/gitDelivery/actionSheetProjection.ts"],"names":[],"mappings":"AAgBA,OAAO,EAIL,KAAK,sBAAsB,EAC3B,KAAK,8BAA8B,EACnC,KAAK,2BAA2B,EAChC,KAAK,sBAAsB,EAE3B,KAAK,yBAAyB,EAC9B,KAAK,wBAAwB,EAE7B,KAAK,6BAA6B,EAClC,KAAK,2BAA2B,EAGhC,KAAK,2BAA2B,EAChC,KAAK,yBAAyB,EAC9B,KAAK,yBAAyB,EAE/B,MAAM,+BAA+B,CAAC;AACvC,OAAO,EAIL,KAAK,mBAAmB,EACzB,MAAM,2BAA2B,CAAC;AAInC,MAAM,WAAW,6BAA6B;IAC5C,QAAQ,CAAC,WAAW,CAAC,EAAE,2BAA2B,GAAG,SAAS,CAAC;IAC/D,QAAQ,CAAC,cAAc,CAAC,EAAE,yBAAyB,GAAG,SAAS,CAAC;IAChE,QAAQ,CAAC,gBAAgB,CAAC,EAAE,2BAA2B,GAAG,SAAS,CAAC;IACpE,QAAQ,CAAC,MAAM,CAAC,EAAE,sBAAsB,GAAG,SAAS,CAAC;CACtD;AAED,MAAM,WAAW,6BAA6B;IAC5C,QAAQ,CAAC,OAAO,CAAC,EAAE,wBAAwB,GAAG,SAAS,CAAC;IACxD,QAAQ,CAAC,QAAQ,CAAC,EAAE,yBAAyB,GAAG,SAAS,CAAC;CAC3D;AAED,MAAM,WAAW,qBAAqB;IACpC,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,cAAc,EAAE,yBAAyB,CAAC;IACnD,QAAQ,CAAC,gBAAgB,EAAE,mBAAmB,CAAC;IAC/C,QAAQ,CAAC,mBAAmB,EAAE,8BAA8B,CAAC;IAC7D,QAAQ,CAAC,WAAW,EAAE,6BAA6B,CAAC;IACpD,QAAQ,CAAC,0BAA0B,EAAE,SAAS,6BAA6B,EAAE,CAAC;IAC9E,QAAQ,CAAC,aAAa,EAAE,6BAA6B,CAAC;IACtD,QAAQ,CAAC,aAAa,EAAE,OAAO,CAAC;IAIhC,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;CACxB;AAkOD;;;GAGG;AACH,wBAAgB,yBAAyB,CAAC,KAAK,EAAE,qBAAqB,GAAG,sBAAsB,CA+B9F;AAID,YAAY,EAAE,2BAA2B,EAAE,CAAC"}
|
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
// Pure projection: content-free kernel facts → UI-safe GitDeliveryActionSheet (Issue #473, Epic #470).
|
|
2
|
+
//
|
|
3
|
+
// This module is a PURE function of (resolvedInputs, worktreeSnapshot, trusted policy packs, approval
|
|
4
|
+
// shape, provider state, providerReady). It runs the kernel preflight (keiko-tools) and the policy
|
|
5
|
+
// evaluator (keiko-contracts), maps their content-free outputs into the action-sheet's typed
|
|
6
|
+
// expected-blocker and recovery-hint vocabularies, and hands the assembled facts to the contract's
|
|
7
|
+
// buildGitDeliveryActionSheet. No IO, no clock, no randomness, no request access — the caller (the
|
|
8
|
+
// route handler) gathers the facts and supplies the actionId and trusted packs.
|
|
9
|
+
//
|
|
10
|
+
// AUTHORITY (AC1): the policy decision, approval necessity, and required approvers are derived ONLY
|
|
11
|
+
// from evaluateGitPolicy over the server's TRUSTED packs. The approval payload is consumed for its
|
|
12
|
+
// SHAPE alone (whether a granted approval is attached); it never asserts the policy decision.
|
|
13
|
+
//
|
|
14
|
+
// Content-free: every value produced here is a count, flag, branch name, or typed/closed-vocabulary
|
|
15
|
+
// code. Never diff content, file paths, secrets, command strings, or raw subprocess output.
|
|
16
|
+
import { buildGitDeliveryActionSheet, gitDeliverySuggestedRecoveryStrategy, evaluateGitPolicy, } from "@oscharko-dev/keiko-contracts";
|
|
17
|
+
import { evaluateGitPreflight, } from "@oscharko-dev/keiko-tools";
|
|
18
|
+
// Demotes an expired granted approval to "not attached" so the action-sheet projection never shows an
|
|
19
|
+
// expired grant as satisfied/ready. The #472 execution kernel re-validates the token and expiry
|
|
20
|
+
// independently before any mutation; this is the preview-time mirror of that check.
|
|
21
|
+
function effectiveApproval(approval, nowMs) {
|
|
22
|
+
if (approval.required && approval.expiresAtMs !== undefined && approval.expiresAtMs <= nowMs) {
|
|
23
|
+
return { required: false };
|
|
24
|
+
}
|
|
25
|
+
return approval;
|
|
26
|
+
}
|
|
27
|
+
// ─── Target-branch derivation (for policy evaluation) ───────────────────────────────
|
|
28
|
+
// The affected branch name the policy evaluator matches branch-pattern rules against. Mirrors the
|
|
29
|
+
// contract's affectedBranchOf but is local so this module owns its own policy-context derivation.
|
|
30
|
+
function targetBranchName(inputs) {
|
|
31
|
+
switch (inputs.kind) {
|
|
32
|
+
case "branch-create":
|
|
33
|
+
return inputs.branchName;
|
|
34
|
+
case "push":
|
|
35
|
+
return inputs.sourceBranchName;
|
|
36
|
+
case "pr-create":
|
|
37
|
+
case "pr-update":
|
|
38
|
+
return inputs.headBranchName;
|
|
39
|
+
default:
|
|
40
|
+
return undefined;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
// ─── Expected-blocker mapping (AC2/AC3) ─────────────────────────────────────────────
|
|
44
|
+
function preflightBlocker(finding) {
|
|
45
|
+
return {
|
|
46
|
+
source: "preflight",
|
|
47
|
+
severity: finding.severity,
|
|
48
|
+
remediation: finding.remediation,
|
|
49
|
+
reasonCode: finding.code,
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
function policyBlocker(decision) {
|
|
53
|
+
if (decision.outcome !== "blocked") {
|
|
54
|
+
return undefined;
|
|
55
|
+
}
|
|
56
|
+
return {
|
|
57
|
+
source: "policy",
|
|
58
|
+
severity: "blocking",
|
|
59
|
+
remediation: "user-actionable",
|
|
60
|
+
reasonCode: decision.reason,
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
const PROVIDER_BLOCKER_KINDS = [
|
|
64
|
+
"merge",
|
|
65
|
+
"pr-create",
|
|
66
|
+
"pr-update",
|
|
67
|
+
];
|
|
68
|
+
function providerBlocker(inputs, mergeReadiness) {
|
|
69
|
+
if (!PROVIDER_BLOCKER_KINDS.includes(inputs.kind)) {
|
|
70
|
+
return undefined;
|
|
71
|
+
}
|
|
72
|
+
if (mergeReadiness?.ready !== false || mergeReadiness.blockingReason === undefined) {
|
|
73
|
+
return undefined;
|
|
74
|
+
}
|
|
75
|
+
return {
|
|
76
|
+
source: "provider",
|
|
77
|
+
severity: "advisory",
|
|
78
|
+
remediation: "user-actionable",
|
|
79
|
+
reasonCode: mergeReadiness.blockingReason,
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
function collectExpectedBlockers(findings, decision, inputs, mergeReadiness) {
|
|
83
|
+
const blockers = findings.map(preflightBlocker);
|
|
84
|
+
const policy = policyBlocker(decision);
|
|
85
|
+
if (policy !== undefined) {
|
|
86
|
+
blockers.push(policy);
|
|
87
|
+
}
|
|
88
|
+
const provider = providerBlocker(inputs, mergeReadiness);
|
|
89
|
+
if (provider !== undefined) {
|
|
90
|
+
blockers.push(provider);
|
|
91
|
+
}
|
|
92
|
+
return blockers;
|
|
93
|
+
}
|
|
94
|
+
// ─── Recovery-hint mapping (AC4 — common failure classes, same surface) ─────────────
|
|
95
|
+
// A deterministic, exhaustive table from each preflight finding code to a recovery action hint. The
|
|
96
|
+
// "recover-via-strategy" hints are produced separately so they can carry a suggestedRecoveryStrategy.
|
|
97
|
+
const STRATEGY_RECOVERY_CODES = new Set([
|
|
98
|
+
"dirty-worktree-impacts-recovery",
|
|
99
|
+
"recovery-target-unset",
|
|
100
|
+
]);
|
|
101
|
+
const FINDING_RECOVERY_HINT = {
|
|
102
|
+
"detached-head": "configure-upstream",
|
|
103
|
+
"branch-already-exists": "adjust-policy-target",
|
|
104
|
+
"base-branch-missing": "adjust-policy-target",
|
|
105
|
+
"switch-target-missing": "adjust-policy-target",
|
|
106
|
+
"no-changes-to-stage": "stage-changes",
|
|
107
|
+
"nothing-staged-to-unstage": "stage-changes",
|
|
108
|
+
"nothing-staged-to-commit": "stage-changes",
|
|
109
|
+
"untracked-files-impacted": "stage-changes",
|
|
110
|
+
"no-upstream-configured": "configure-upstream",
|
|
111
|
+
"nothing-to-push": "retry",
|
|
112
|
+
"non-fast-forward": "resolve-conflicts",
|
|
113
|
+
"remote-alias-missing": "configure-upstream",
|
|
114
|
+
"remote-unreachable": "retry",
|
|
115
|
+
"operation-in-progress": "abort-in-progress-operation",
|
|
116
|
+
"no-operation-to-abort": "retry",
|
|
117
|
+
"recovery-target-unset": "recover-via-strategy",
|
|
118
|
+
"dirty-worktree-impacts-recovery": "recover-via-strategy",
|
|
119
|
+
};
|
|
120
|
+
function worktreeIsDirty(snapshot) {
|
|
121
|
+
return (snapshot.stagedFileCount > 0 ||
|
|
122
|
+
snapshot.unstagedFileCount > 0 ||
|
|
123
|
+
snapshot.untrackedFileCount > 0);
|
|
124
|
+
}
|
|
125
|
+
function recoveryHintForFinding(finding, inputs, snapshot) {
|
|
126
|
+
if (STRATEGY_RECOVERY_CODES.has(finding.code)) {
|
|
127
|
+
return {
|
|
128
|
+
actionHint: "recover-via-strategy",
|
|
129
|
+
remediation: finding.remediation,
|
|
130
|
+
suggestedRecoveryStrategy: gitDeliverySuggestedRecoveryStrategy(inputs.kind, worktreeIsDirty(snapshot)),
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
return { actionHint: FINDING_RECOVERY_HINT[finding.code], remediation: finding.remediation };
|
|
134
|
+
}
|
|
135
|
+
function policyRecoveryHint(decision) {
|
|
136
|
+
if (decision.outcome === "approval-gated") {
|
|
137
|
+
return { actionHint: "request-approval", remediation: "user-actionable" };
|
|
138
|
+
}
|
|
139
|
+
if (decision.outcome === "blocked") {
|
|
140
|
+
const actionHint = decision.reason === "approval-expired" ? "request-approval" : "adjust-policy-target";
|
|
141
|
+
return { actionHint, remediation: "user-actionable" };
|
|
142
|
+
}
|
|
143
|
+
return undefined;
|
|
144
|
+
}
|
|
145
|
+
// De-duplicates recovery hints on a stable signature (action + strategy) so the same class of fix is
|
|
146
|
+
// surfaced once even when several findings map to it. First occurrence wins (order-preserving).
|
|
147
|
+
function dedupeRecoveryHints(hints) {
|
|
148
|
+
const seen = new Set();
|
|
149
|
+
const out = [];
|
|
150
|
+
for (const hint of hints) {
|
|
151
|
+
const signature = `${hint.actionHint}:${hint.suggestedRecoveryStrategy ?? ""}`;
|
|
152
|
+
if (!seen.has(signature)) {
|
|
153
|
+
seen.add(signature);
|
|
154
|
+
out.push(hint);
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
return out;
|
|
158
|
+
}
|
|
159
|
+
function buildRecoveryHints(findings, decision, inputs, snapshot, providerReady) {
|
|
160
|
+
const hints = findings.map((finding) => recoveryHintForFinding(finding, inputs, snapshot));
|
|
161
|
+
const policy = policyRecoveryHint(decision);
|
|
162
|
+
if (policy !== undefined) {
|
|
163
|
+
hints.push(policy);
|
|
164
|
+
}
|
|
165
|
+
if (!providerReady) {
|
|
166
|
+
hints.push({ actionHint: "wait-for-provider", remediation: "internal" });
|
|
167
|
+
}
|
|
168
|
+
return dedupeRecoveryHints(hints);
|
|
169
|
+
}
|
|
170
|
+
function definedProviderState(state) {
|
|
171
|
+
const out = {};
|
|
172
|
+
if (state.pullRequest !== undefined)
|
|
173
|
+
out.pullRequest = state.pullRequest;
|
|
174
|
+
if (state.mergeReadiness !== undefined)
|
|
175
|
+
out.mergeReadiness = state.mergeReadiness;
|
|
176
|
+
if (state.branchProtection !== undefined)
|
|
177
|
+
out.branchProtection = state.branchProtection;
|
|
178
|
+
if (state.checks !== undefined)
|
|
179
|
+
out.checks = state.checks;
|
|
180
|
+
return out;
|
|
181
|
+
}
|
|
182
|
+
/**
|
|
183
|
+
* The pure projection. Runs preflight + policy over the supplied content-free facts and assembles the
|
|
184
|
+
* UI-safe action sheet. Deterministic: identical facts always yield an identical sheet.
|
|
185
|
+
*/
|
|
186
|
+
export function buildActionSheetFromFacts(facts) {
|
|
187
|
+
const { resolvedInputs, worktreeSnapshot, providerState } = facts;
|
|
188
|
+
const preflight = evaluateGitPreflight(resolvedInputs, worktreeSnapshot);
|
|
189
|
+
const policyDecision = evaluateGitPolicy(facts.policyPacks.orgPack, facts.policyPacks.repoPack, {
|
|
190
|
+
actionKind: resolvedInputs.kind,
|
|
191
|
+
targetBranchName: targetBranchName(resolvedInputs),
|
|
192
|
+
activeProviderCapabilities: facts.activeProviderCapabilities,
|
|
193
|
+
});
|
|
194
|
+
const expectedBlockers = collectExpectedBlockers(preflight.findings, policyDecision, resolvedInputs, providerState.mergeReadiness);
|
|
195
|
+
const recovery = buildRecoveryHints(preflight.findings, policyDecision, resolvedInputs, worktreeSnapshot, facts.providerReady);
|
|
196
|
+
return buildGitDeliveryActionSheet({
|
|
197
|
+
actionId: facts.actionId,
|
|
198
|
+
resolvedInputs,
|
|
199
|
+
policyDecision,
|
|
200
|
+
approvalRequirement: effectiveApproval(facts.approvalRequirement, facts.nowMs),
|
|
201
|
+
providerReady: facts.providerReady,
|
|
202
|
+
expectedBlockers,
|
|
203
|
+
recovery,
|
|
204
|
+
...definedProviderState(providerState),
|
|
205
|
+
});
|
|
206
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { type GitDeliveryApprovalRequirement, type GitDeliveryProviderCapability, type GitDeliveryResolvedInputs } from "@oscharko-dev/keiko-contracts";
|
|
2
|
+
import type { GitWorktreeSnapshot } from "@oscharko-dev/keiko-tools";
|
|
3
|
+
import type { RouteContext, RouteDefinition, RouteResult } from "../routes.js";
|
|
4
|
+
import type { UiHandlerDeps } from "../deps.js";
|
|
5
|
+
import { type GitDeliveryProviderStateFacts, type GitDeliveryTrustedPolicyPacks } from "./actionSheetProjection.js";
|
|
6
|
+
export type GitDeliveryActionSheetErrorCode = "GIT_DELIVERY_ACTION_SHEET_BAD_REQUEST" | "GIT_DELIVERY_ACTION_SHEET_PAYLOAD_TOO_LARGE" | "GIT_DELIVERY_ACTION_SHEET_FORBIDDEN_PAYLOAD";
|
|
7
|
+
export interface GitDeliveryActionSheetErrorBody {
|
|
8
|
+
readonly error: {
|
|
9
|
+
readonly code: GitDeliveryActionSheetErrorCode;
|
|
10
|
+
readonly message: string;
|
|
11
|
+
};
|
|
12
|
+
}
|
|
13
|
+
interface ValidatedRequest {
|
|
14
|
+
readonly resolvedInputs: GitDeliveryResolvedInputs;
|
|
15
|
+
readonly worktreeSnapshot: GitWorktreeSnapshot;
|
|
16
|
+
readonly approvalRequirement: GitDeliveryApprovalRequirement;
|
|
17
|
+
readonly providerState: GitDeliveryProviderStateFacts;
|
|
18
|
+
readonly activeProviderCapabilities: readonly GitDeliveryProviderCapability[];
|
|
19
|
+
}
|
|
20
|
+
export interface GitDeliveryActionSheetRouteOptions {
|
|
21
|
+
readonly idGenerator?: (request: ValidatedRequest) => string;
|
|
22
|
+
readonly policyPacks?: (deps: UiHandlerDeps) => GitDeliveryTrustedPolicyPacks;
|
|
23
|
+
readonly now?: () => number;
|
|
24
|
+
}
|
|
25
|
+
export declare const createHandleGitDeliveryActionSheet: (options?: GitDeliveryActionSheetRouteOptions) => ((ctx: RouteContext, deps: UiHandlerDeps) => Promise<RouteResult>);
|
|
26
|
+
export declare const handleGitDeliveryActionSheet: (ctx: RouteContext, deps: UiHandlerDeps) => Promise<RouteResult>;
|
|
27
|
+
export declare const GIT_DELIVERY_ACTION_SHEET_ROUTE_GROUP: readonly RouteDefinition[];
|
|
28
|
+
export {};
|
|
29
|
+
//# sourceMappingURL=actionSheetRoutes.d.ts.map
|