glirastes 0.3.0 → 0.3.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +86 -0
- package/README.md +154 -9
- package/codegen/package.json +5 -0
- package/dist/cjs/codegen/asset-sync.d.ts +20 -0
- package/dist/cjs/codegen/asset-sync.js +28 -0
- package/dist/cjs/codegen/coverage.d.ts +66 -0
- package/dist/cjs/codegen/coverage.js +408 -0
- package/dist/cjs/codegen/endpoint-scanner.d.ts +16 -0
- package/dist/cjs/codegen/endpoint-scanner.js +197 -0
- package/dist/cjs/codegen/format.d.ts +17 -0
- package/dist/cjs/codegen/format.js +34 -0
- package/dist/cjs/codegen/fs-utils.d.ts +45 -0
- package/dist/cjs/codegen/fs-utils.js +165 -0
- package/dist/cjs/codegen/index.d.ts +13 -0
- package/dist/cjs/codegen/index.js +33 -0
- package/dist/cjs/codegen/mcp-generator.d.ts +50 -0
- package/dist/cjs/codegen/mcp-generator.js +803 -0
- package/dist/cjs/codegen/module-generator.d.ts +11 -0
- package/dist/cjs/codegen/module-generator.js +353 -0
- package/dist/cjs/codegen/nestjs-scanner.d.ts +43 -0
- package/dist/cjs/codegen/nestjs-scanner.js +241 -0
- package/dist/cjs/codegen/output-recommender.d.ts +23 -0
- package/dist/cjs/codegen/output-recommender.js +33 -0
- package/dist/cjs/codegen/scaffolder.d.ts +36 -0
- package/dist/cjs/codegen/scaffolder.js +262 -0
- package/dist/cjs/codegen/skill-generator.d.ts +49 -0
- package/dist/cjs/codegen/skill-generator.js +967 -0
- package/dist/cjs/codegen/types.d.ts +52 -0
- package/dist/cjs/codegen/types.js +16 -0
- package/dist/cjs/codegen/ui-scanner.d.ts +24 -0
- package/dist/cjs/codegen/ui-scanner.js +202 -0
- package/dist/cjs/codegen/validator.d.ts +35 -0
- package/dist/cjs/codegen/validator.js +292 -0
- package/dist/cjs/index.d.ts +1 -0
- package/dist/cjs/index.js +23 -0
- package/dist/cjs/openapi/index.d.ts +78 -0
- package/dist/cjs/openapi/index.js +347 -0
- package/dist/cjs/package.json +1 -0
- package/dist/cjs/react/audio-worklet-processor.d.ts +16 -0
- package/dist/cjs/react/audio-worklet-processor.js +27 -0
- package/dist/cjs/react/bridges/rest-session-bridge.d.ts +113 -0
- package/dist/cjs/react/bridges/rest-session-bridge.js +209 -0
- package/dist/cjs/react/components/ai-chat-panel.d.ts +12 -0
- package/dist/cjs/react/components/ai-chat-panel.js +67 -0
- package/dist/cjs/react/components/ai-trigger-button.d.ts +2 -0
- package/dist/cjs/react/components/ai-trigger-button.js +239 -0
- package/dist/cjs/react/components/approval-card.d.ts +9 -0
- package/dist/cjs/react/components/approval-card.js +24 -0
- package/dist/cjs/react/components/bulk-approval-card.d.ts +9 -0
- package/dist/cjs/react/components/bulk-approval-card.js +36 -0
- package/dist/cjs/react/components/chat-input.d.ts +10 -0
- package/dist/cjs/react/components/chat-input.js +60 -0
- package/dist/cjs/react/components/chat-window.d.ts +2 -0
- package/dist/cjs/react/components/chat-window.js +134 -0
- package/dist/cjs/react/components/confirm-dialog.d.ts +32 -0
- package/dist/cjs/react/components/confirm-dialog.js +48 -0
- package/dist/cjs/react/components/floating-chat-ui.d.ts +90 -0
- package/dist/cjs/react/components/floating-chat-ui.js +163 -0
- package/dist/cjs/react/components/mention-chip.d.ts +16 -0
- package/dist/cjs/react/components/mention-chip.js +42 -0
- package/dist/cjs/react/components/mention-input.d.ts +8 -0
- package/dist/cjs/react/components/mention-input.js +117 -0
- package/dist/cjs/react/components/message-bubble.d.ts +11 -0
- package/dist/cjs/react/components/message-bubble.js +171 -0
- package/dist/cjs/react/components/message-list.d.ts +8 -0
- package/dist/cjs/react/components/message-list.js +52 -0
- package/dist/cjs/react/components/pipeline-timeline.d.ts +7 -0
- package/dist/cjs/react/components/pipeline-timeline.js +72 -0
- package/dist/cjs/react/components/recording-bar.d.ts +2 -0
- package/dist/cjs/react/components/recording-bar.js +81 -0
- package/dist/cjs/react/components/recording-controls.d.ts +18 -0
- package/dist/cjs/react/components/recording-controls.js +38 -0
- package/dist/cjs/react/components/rich-mention-input.d.ts +2 -0
- package/dist/cjs/react/components/rich-mention-input.js +571 -0
- package/dist/cjs/react/components/session-switcher.d.ts +14 -0
- package/dist/cjs/react/components/session-switcher.js +133 -0
- package/dist/cjs/react/components/suggestion-bar.d.ts +9 -0
- package/dist/cjs/react/components/suggestion-bar.js +47 -0
- package/dist/cjs/react/components/suggestion-chip.d.ts +9 -0
- package/dist/cjs/react/components/suggestion-chip.js +14 -0
- package/dist/cjs/react/components/voice-input-addon.d.ts +13 -0
- package/dist/cjs/react/components/voice-input-addon.js +31 -0
- package/dist/cjs/react/components/voice-input-button.d.ts +2 -0
- package/dist/cjs/react/components/voice-input-button.js +55 -0
- package/dist/cjs/react/components/wavesurfer-loader.d.ts +5 -0
- package/dist/cjs/react/components/wavesurfer-loader.js +36 -0
- package/dist/cjs/react/hooks/use-ai-chat.d.ts +22 -0
- package/dist/cjs/react/hooks/use-ai-chat.js +51 -0
- package/dist/cjs/react/hooks/use-approvals.d.ts +23 -0
- package/dist/cjs/react/hooks/use-approvals.js +63 -0
- package/dist/cjs/react/hooks/use-deepgram-transcription.d.ts +2 -0
- package/dist/cjs/react/hooks/use-deepgram-transcription.js +329 -0
- package/dist/cjs/react/hooks/use-drag-resize-panel.d.ts +48 -0
- package/dist/cjs/react/hooks/use-drag-resize-panel.js +308 -0
- package/dist/cjs/react/hooks/use-draggable-position.d.ts +25 -0
- package/dist/cjs/react/hooks/use-draggable-position.js +134 -0
- package/dist/cjs/react/hooks/use-keyboard-shortcut.d.ts +20 -0
- package/dist/cjs/react/hooks/use-keyboard-shortcut.js +93 -0
- package/dist/cjs/react/hooks/use-mentions.d.ts +23 -0
- package/dist/cjs/react/hooks/use-mentions.js +42 -0
- package/dist/cjs/react/hooks/use-suggestions.d.ts +22 -0
- package/dist/cjs/react/hooks/use-suggestions.js +39 -0
- package/dist/cjs/react/index.d.ts +43 -0
- package/dist/cjs/react/index.js +111 -0
- package/dist/cjs/react/provider/ai-chat-provider.d.ts +45 -0
- package/dist/cjs/react/provider/ai-chat-provider.js +1024 -0
- package/dist/cjs/react/provider/chat-context.d.ts +84 -0
- package/dist/cjs/react/provider/chat-context.js +13 -0
- package/dist/cjs/react/template.d.ts +84 -0
- package/dist/cjs/react/template.js +90 -0
- package/dist/cjs/react/themes.d.ts +24 -0
- package/dist/cjs/react/themes.js +273 -0
- package/dist/cjs/react/transports/langgraph/index.d.ts +2 -0
- package/dist/cjs/react/transports/langgraph/index.js +8 -0
- package/dist/cjs/react/transports/langgraph/langgraph-ai-chat.d.ts +62 -0
- package/dist/cjs/react/transports/langgraph/langgraph-ai-chat.js +94 -0
- package/dist/cjs/react/transports/langgraph.d.ts +71 -0
- package/dist/cjs/react/transports/langgraph.js +369 -0
- package/dist/cjs/react/transports/vercel/index.d.ts +2 -0
- package/dist/cjs/react/transports/vercel/index.js +8 -0
- package/dist/cjs/react/transports/vercel/vercel-ai-chat.d.ts +49 -0
- package/dist/cjs/react/transports/vercel/vercel-ai-chat.js +81 -0
- package/dist/cjs/react/transports/vercel.d.ts +30 -0
- package/dist/cjs/react/transports/vercel.js +69 -0
- package/dist/cjs/react/types.d.ts +760 -0
- package/dist/cjs/react/types.js +2 -0
- package/dist/cjs/react/ui.d.ts +73 -0
- package/dist/cjs/react/ui.js +183 -0
- package/dist/cjs/react/utils/create-approval-description.d.ts +53 -0
- package/dist/cjs/react/utils/create-approval-description.js +122 -0
- package/dist/cjs/react/utils/create-tool-result-handler.d.ts +90 -0
- package/dist/cjs/react/utils/create-tool-result-handler.js +168 -0
- package/dist/cjs/react/utils/mention-markup.d.ts +13 -0
- package/dist/cjs/react/utils/mention-markup.js +117 -0
- package/dist/cjs/react/utils/merge-pipeline-steps.d.ts +19 -0
- package/dist/cjs/react/utils/merge-pipeline-steps.js +81 -0
- package/dist/cjs/react/utils/sanitize-stored-messages.d.ts +23 -0
- package/dist/cjs/react/utils/sanitize-stored-messages.js +52 -0
- package/dist/cjs/server/adapters/nestjs/chat/ai-chat-auth.guard.d.ts +10 -0
- package/dist/cjs/server/adapters/nestjs/chat/ai-chat-auth.guard.js +42 -0
- package/dist/cjs/server/adapters/nestjs/chat/ai-chat-module-options.interface.d.ts +32 -0
- package/dist/cjs/server/adapters/nestjs/chat/ai-chat-module-options.interface.js +4 -0
- package/dist/cjs/server/adapters/nestjs/chat/ai-chat.controller.d.ts +11 -0
- package/dist/cjs/server/adapters/nestjs/chat/ai-chat.controller.js +80 -0
- package/dist/cjs/server/adapters/nestjs/chat/ai-chat.module.d.ts +9 -0
- package/dist/cjs/server/adapters/nestjs/chat/ai-chat.module.js +119 -0
- package/dist/cjs/server/adapters/nestjs/chat/ai-chat.service.d.ts +13 -0
- package/dist/cjs/server/adapters/nestjs/chat/ai-chat.service.js +131 -0
- package/dist/cjs/server/adapters/nestjs/chat/dto/ai-chat-request.dto.d.ts +7 -0
- package/dist/cjs/server/adapters/nestjs/chat/dto/ai-chat-request.dto.js +52 -0
- package/dist/cjs/server/adapters/nestjs/chat/dto/chat-message-part.dto.d.ts +19 -0
- package/dist/cjs/server/adapters/nestjs/chat/dto/chat-message-part.dto.js +108 -0
- package/dist/cjs/server/adapters/nestjs/chat/dto/chat-message.dto.d.ts +8 -0
- package/dist/cjs/server/adapters/nestjs/chat/dto/chat-message.dto.js +59 -0
- package/dist/cjs/server/adapters/nestjs/chat/dto/index.d.ts +3 -0
- package/dist/cjs/server/adapters/nestjs/chat/dto/index.js +9 -0
- package/dist/cjs/server/adapters/nestjs/chat/index.d.ts +3 -0
- package/dist/cjs/server/adapters/nestjs/chat/index.js +9 -0
- package/dist/cjs/server/adapters/nestjs/chat/speech-stream.gateway.d.ts +4 -0
- package/dist/cjs/server/adapters/nestjs/chat/speech-stream.gateway.js +73 -0
- package/dist/cjs/server/adapters/nestjs/chat/ws-loader.d.ts +5 -0
- package/dist/cjs/server/adapters/nestjs/chat/ws-loader.js +72 -0
- package/dist/cjs/server/adapters/nestjs/decorators/ai-module.decorator.d.ts +6 -0
- package/dist/cjs/server/adapters/nestjs/decorators/ai-module.decorator.js +13 -0
- package/dist/cjs/server/adapters/nestjs/decorators/ai-param.decorator.d.ts +5 -0
- package/dist/cjs/server/adapters/nestjs/decorators/ai-param.decorator.js +15 -0
- package/dist/cjs/server/adapters/nestjs/decorators/ai-tool.decorator.d.ts +6 -0
- package/dist/cjs/server/adapters/nestjs/decorators/ai-tool.decorator.js +22 -0
- package/dist/cjs/server/adapters/nestjs/decorators/constants.d.ts +4 -0
- package/dist/cjs/server/adapters/nestjs/decorators/constants.js +7 -0
- package/dist/cjs/server/adapters/nestjs/index.d.ts +14 -0
- package/dist/cjs/server/adapters/nestjs/index.js +42 -0
- package/dist/cjs/server/adapters/nestjs/metadata.d.ts +39 -0
- package/dist/cjs/server/adapters/nestjs/metadata.js +36 -0
- package/dist/cjs/server/adapters/nestjs/module/ai-tools-explorer.service.d.ts +36 -0
- package/dist/cjs/server/adapters/nestjs/module/ai-tools-explorer.service.js +159 -0
- package/dist/cjs/server/adapters/nestjs/module/ai-tools.module.d.ts +37 -0
- package/dist/cjs/server/adapters/nestjs/module/ai-tools.module.js +44 -0
- package/dist/cjs/server/adapters/nestjs/module/ai-tools.service.d.ts +28 -0
- package/dist/cjs/server/adapters/nestjs/module/ai-tools.service.js +41 -0
- package/dist/cjs/server/adapters/nestjs/scanner/nestjs-scanner.d.ts +16 -0
- package/dist/cjs/server/adapters/nestjs/scanner/nestjs-scanner.js +117 -0
- package/dist/cjs/server/adapters/nestjs/scanner/schema-builder.d.ts +26 -0
- package/dist/cjs/server/adapters/nestjs/scanner/schema-builder.js +259 -0
- package/dist/cjs/server/adapters/nestjs/swagger.d.ts +7 -0
- package/dist/cjs/server/adapters/nestjs/swagger.js +39 -0
- package/dist/cjs/server/adapters/nextjs/context-helpers.d.ts +22 -0
- package/dist/cjs/server/adapters/nextjs/context-helpers.js +60 -0
- package/dist/cjs/server/adapters/nextjs/create-ai-chat-handler.d.ts +94 -0
- package/dist/cjs/server/adapters/nextjs/create-ai-chat-handler.js +965 -0
- package/dist/cjs/server/adapters/nextjs/filter-fallback-error.d.ts +11 -0
- package/dist/cjs/server/adapters/nextjs/filter-fallback-error.js +41 -0
- package/dist/cjs/server/adapters/nextjs/index.d.ts +8 -0
- package/dist/cjs/server/adapters/nextjs/index.js +22 -0
- package/dist/cjs/server/adapters/nextjs/prepare-model-messages.d.ts +58 -0
- package/dist/cjs/server/adapters/nextjs/prepare-model-messages.js +114 -0
- package/dist/cjs/server/adapters/nextjs/sanitize-model-messages.d.ts +64 -0
- package/dist/cjs/server/adapters/nextjs/sanitize-model-messages.js +489 -0
- package/dist/cjs/server/adapters/nextjs/server-action-helper.d.ts +7 -0
- package/dist/cjs/server/adapters/nextjs/server-action-helper.js +40 -0
- package/dist/cjs/server/adapters/nextjs/types.d.ts +246 -0
- package/dist/cjs/server/adapters/nextjs/types.js +2 -0
- package/dist/cjs/server/adapters/nextjs/with-ai-tool.d.ts +9 -0
- package/dist/cjs/server/adapters/nextjs/with-ai-tool.js +12 -0
- package/dist/cjs/server/core/followup-tool.d.ts +9 -0
- package/dist/cjs/server/core/followup-tool.js +82 -0
- package/dist/cjs/server/core/index.d.ts +139 -0
- package/dist/cjs/server/core/index.js +818 -0
- package/dist/cjs/server/core/intent-classifier.d.ts +94 -0
- package/dist/cjs/server/core/intent-classifier.js +324 -0
- package/dist/cjs/server/core/model-selector.d.ts +12 -0
- package/dist/cjs/server/core/model-selector.js +48 -0
- package/dist/cjs/server/core/module-router.d.ts +14 -0
- package/dist/cjs/server/core/module-router.js +110 -0
- package/dist/cjs/server/core/zod-compact.d.ts +18 -0
- package/dist/cjs/server/core/zod-compact.js +129 -0
- package/dist/cjs/server/index.d.ts +6 -0
- package/dist/cjs/server/index.js +28 -0
- package/dist/cjs/server/lancer/cache.d.ts +9 -0
- package/dist/cjs/server/lancer/cache.js +33 -0
- package/dist/cjs/server/lancer/client.d.ts +2 -0
- package/dist/cjs/server/lancer/client.js +279 -0
- package/dist/cjs/server/lancer/errors.d.ts +13 -0
- package/dist/cjs/server/lancer/errors.js +21 -0
- package/dist/cjs/server/lancer/index.d.ts +3 -0
- package/dist/cjs/server/lancer/index.js +7 -0
- package/dist/cjs/server/lancer/telemetry-buffer.d.ts +15 -0
- package/dist/cjs/server/lancer/telemetry-buffer.js +64 -0
- package/dist/cjs/server/lancer/types.d.ts +201 -0
- package/dist/cjs/server/lancer/types.js +5 -0
- package/dist/cjs/server/pii-shield/aegis-detector.d.ts +26 -0
- package/dist/cjs/server/pii-shield/aegis-detector.js +26 -0
- package/dist/cjs/server/pii-shield/create-pii-shield.d.ts +2 -0
- package/dist/cjs/server/pii-shield/create-pii-shield.js +191 -0
- package/dist/cjs/server/pii-shield/deep-walk.d.ts +1 -0
- package/dist/cjs/server/pii-shield/deep-walk.js +19 -0
- package/dist/cjs/server/pii-shield/fake-data.d.ts +5 -0
- package/dist/cjs/server/pii-shield/fake-data.js +170 -0
- package/dist/cjs/server/pii-shield/index.d.ts +4 -0
- package/dist/cjs/server/pii-shield/index.js +9 -0
- package/dist/cjs/server/pii-shield/leakage.d.ts +1 -0
- package/dist/cjs/server/pii-shield/leakage.js +15 -0
- package/dist/cjs/server/pii-shield/session-mapping.d.ts +12 -0
- package/dist/cjs/server/pii-shield/session-mapping.js +70 -0
- package/dist/cjs/server/pipeline.d.ts +26 -0
- package/dist/cjs/server/pipeline.js +127 -0
- package/dist/cjs/server/testing/approvals.d.ts +11 -0
- package/dist/cjs/server/testing/approvals.js +41 -0
- package/dist/cjs/server/testing/combination.d.ts +3 -0
- package/dist/cjs/server/testing/combination.js +83 -0
- package/dist/cjs/server/testing/create-test-suite.d.ts +29 -0
- package/dist/cjs/server/testing/create-test-suite.js +81 -0
- package/dist/cjs/server/testing/edge-cases.d.ts +8 -0
- package/dist/cjs/server/testing/edge-cases.js +277 -0
- package/dist/cjs/server/testing/guardrails.d.ts +14 -0
- package/dist/cjs/server/testing/guardrails.js +152 -0
- package/dist/cjs/server/testing/helpers.d.ts +14 -0
- package/dist/cjs/server/testing/helpers.js +119 -0
- package/dist/cjs/server/testing/index.d.ts +12 -0
- package/dist/cjs/server/testing/index.js +37 -0
- package/dist/cjs/server/testing/mock-executor.d.ts +8 -0
- package/dist/cjs/server/testing/mock-executor.js +40 -0
- package/dist/cjs/server/testing/regression.d.ts +7 -0
- package/dist/cjs/server/testing/regression.js +27 -0
- package/dist/cjs/server/testing/routing.d.ts +12 -0
- package/dist/cjs/server/testing/routing.js +63 -0
- package/dist/cjs/server/testing/schema-consistency.d.ts +6 -0
- package/dist/cjs/server/testing/schema-consistency.js +56 -0
- package/dist/cjs/server/testing/smoke.d.ts +11 -0
- package/dist/cjs/server/testing/smoke.js +64 -0
- package/dist/cjs/server/testing/types.d.ts +156 -0
- package/dist/cjs/server/testing/types.js +2 -0
- package/dist/cjs/types.d.ts +696 -0
- package/dist/cjs/types.js +91 -0
- package/dist/cli/commands/install-skills.d.ts +17 -0
- package/dist/cli/commands/install-skills.js +185 -0
- package/dist/cli/commands/install-skills.js.map +1 -0
- package/dist/cli/index.js +25 -0
- package/dist/cli/index.js.map +1 -1
- package/dist/react/components/ai-trigger-button.js +4 -6
- package/dist/react/components/ai-trigger-button.js.map +1 -1
- package/dist/react/components/recording-bar.js +4 -6
- package/dist/react/components/recording-bar.js.map +1 -1
- package/dist/react/components/wavesurfer-loader.d.ts +5 -0
- package/dist/react/components/wavesurfer-loader.js +34 -0
- package/dist/react/components/wavesurfer-loader.js.map +1 -0
- package/dist/server/adapters/nestjs/chat/ai-chat.module.d.ts +1 -1
- package/dist/server/adapters/nestjs/chat/ai-chat.module.js +3 -2
- package/dist/server/adapters/nestjs/chat/ai-chat.module.js.map +1 -1
- package/dist/server/adapters/nestjs/chat/speech-stream.gateway.d.ts +1 -1
- package/dist/server/adapters/nestjs/chat/speech-stream.gateway.js +10 -8
- package/dist/server/adapters/nestjs/chat/speech-stream.gateway.js.map +1 -1
- package/dist/server/adapters/nestjs/chat/ws-loader.d.ts +5 -0
- package/dist/server/adapters/nestjs/chat/ws-loader.js +37 -0
- package/dist/server/adapters/nestjs/chat/ws-loader.js.map +1 -0
- package/openapi/package.json +5 -0
- package/package.json +59 -16
- package/react/langgraph/package.json +5 -0
- package/react/package.json +5 -0
- package/react/vercel/package.json +5 -0
- package/server/nestjs/package.json +5 -0
- package/server/nextjs/package.json +5 -0
- package/server/package.json +5 -0
- package/server/testing/package.json +5 -0
- package/skills/README.md +107 -0
- package/skills/building-glirastes-chat-ui/SKILL.md +378 -0
- package/skills/integrating-glirastes-nestjs/SKILL.md +346 -0
- package/skills/integrating-glirastes-nextjs/SKILL.md +312 -0
- package/skills/integrating-glirastes-nextjs-with-nestjs-backend/SKILL.md +359 -0
- package/skills/maintaining-glirastes-tools/SKILL.md +467 -0
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.createToolResultHandler = createToolResultHandler;
|
|
4
|
+
function isRecord(value) {
|
|
5
|
+
return typeof value === 'object' && value !== null;
|
|
6
|
+
}
|
|
7
|
+
function extractUiAction(result) {
|
|
8
|
+
if (result.uiAction !== undefined)
|
|
9
|
+
return result.uiAction;
|
|
10
|
+
if (isRecord(result.data) && result.data.uiAction !== undefined) {
|
|
11
|
+
return result.data.uiAction;
|
|
12
|
+
}
|
|
13
|
+
return undefined;
|
|
14
|
+
}
|
|
15
|
+
function hasSuccessfulResult(result) {
|
|
16
|
+
if (result.success === true)
|
|
17
|
+
return true;
|
|
18
|
+
return isRecord(result.data) && result.data.success === true;
|
|
19
|
+
}
|
|
20
|
+
function safeStringify(value) {
|
|
21
|
+
try {
|
|
22
|
+
return JSON.stringify(value);
|
|
23
|
+
}
|
|
24
|
+
catch {
|
|
25
|
+
return '[unserializable]';
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Create a tool result handler that processes tool outputs for UI-side actions.
|
|
30
|
+
*
|
|
31
|
+
* Extracts `uiAction` from tool results, validates against an optional schema,
|
|
32
|
+
* and routes to the appropriate handler based on action type.
|
|
33
|
+
*
|
|
34
|
+
* Supports convention-based entity actions (`{entity}-created`, `{entity}-updated`,
|
|
35
|
+
* `{entity}-deleted`), `run-client-action`, and custom action handlers.
|
|
36
|
+
*
|
|
37
|
+
* @example
|
|
38
|
+
* ```ts
|
|
39
|
+
* import { createToolResultHandler } from '../index.js';
|
|
40
|
+
* import { dispatchAiClientAction } from '../ui/index.js';
|
|
41
|
+
*
|
|
42
|
+
* const handleToolResult = createToolResultHandler({
|
|
43
|
+
* uiActionSchema: mySchema,
|
|
44
|
+
* entityListDestinations: { task: '/tasks', group: '/groups' },
|
|
45
|
+
* onNavigate: (dest) => router.push(dest),
|
|
46
|
+
* onOpenDetail: (entity, id) => openPanel(entity, id),
|
|
47
|
+
* onClientAction: (actionId, payload) => dispatchAiClientAction({ actionId, payload }),
|
|
48
|
+
* onSuccess: () => window.dispatchEvent(new CustomEvent('data-refresh')),
|
|
49
|
+
* actionHandlers: {
|
|
50
|
+
* 'set-filters': (action) => {
|
|
51
|
+
* window.dispatchEvent(new CustomEvent('ai-set-filters', { detail: action.filters }));
|
|
52
|
+
* },
|
|
53
|
+
* },
|
|
54
|
+
* });
|
|
55
|
+
*
|
|
56
|
+
* // Wire into AiChatProvider or use in useEffect:
|
|
57
|
+
* <AiChatProvider onToolResult={handleToolResult} ... />
|
|
58
|
+
* ```
|
|
59
|
+
*/
|
|
60
|
+
function createToolResultHandler(config) {
|
|
61
|
+
const { uiActionSchema, actionHandlers = {}, entityListDestinations = {}, onNavigate, onOpenDetail, onClientAction, onSuccess, toolFallbacks = {}, clientActionDedupe, } = config;
|
|
62
|
+
const dedupeEnabled = clientActionDedupe?.enabled ?? false;
|
|
63
|
+
const dedupeWindowMs = clientActionDedupe?.windowMs ?? 1500;
|
|
64
|
+
const dedupeActionIds = clientActionDedupe?.actionIds ?? ['navigate'];
|
|
65
|
+
const recentClientActions = new Map();
|
|
66
|
+
return (toolName, result) => {
|
|
67
|
+
if (!isRecord(result))
|
|
68
|
+
return;
|
|
69
|
+
let handledByAction = false;
|
|
70
|
+
const uiActionCandidate = extractUiAction(result);
|
|
71
|
+
if (uiActionCandidate !== undefined) {
|
|
72
|
+
// Normalize to array for uniform processing (supports compound actions)
|
|
73
|
+
const candidates = Array.isArray(uiActionCandidate)
|
|
74
|
+
? uiActionCandidate
|
|
75
|
+
: [uiActionCandidate];
|
|
76
|
+
for (const candidate of candidates) {
|
|
77
|
+
// Validate if schema provided
|
|
78
|
+
let uiAction;
|
|
79
|
+
if (uiActionSchema) {
|
|
80
|
+
const parsed = uiActionSchema.safeParse(candidate);
|
|
81
|
+
if (!parsed.success) {
|
|
82
|
+
console.warn('[AI Chat] Invalid uiAction dropped:', parsed.error.issues);
|
|
83
|
+
continue;
|
|
84
|
+
}
|
|
85
|
+
uiAction = parsed.data;
|
|
86
|
+
}
|
|
87
|
+
else {
|
|
88
|
+
if (!isRecord(candidate))
|
|
89
|
+
continue;
|
|
90
|
+
uiAction = candidate;
|
|
91
|
+
}
|
|
92
|
+
const actionType = typeof uiAction.type === 'string' ? uiAction.type : '';
|
|
93
|
+
// Convention-based entity actions: {entity}-{created|updated|deleted}
|
|
94
|
+
const entityMatch = actionType.match(/^(\w+)-(created|updated|deleted)$/);
|
|
95
|
+
if (entityMatch) {
|
|
96
|
+
const [, entity, action] = entityMatch;
|
|
97
|
+
const idKey = `${entity}Id`;
|
|
98
|
+
const entityId = uiAction[idKey];
|
|
99
|
+
if (action === 'created' && entityListDestinations[entity] && onNavigate) {
|
|
100
|
+
onNavigate(entityListDestinations[entity], entity);
|
|
101
|
+
handledByAction = true;
|
|
102
|
+
}
|
|
103
|
+
if (action === 'updated' && typeof entityId === 'string' && onOpenDetail) {
|
|
104
|
+
onOpenDetail(entity, entityId);
|
|
105
|
+
handledByAction = true;
|
|
106
|
+
}
|
|
107
|
+
if (action === 'deleted' && entityListDestinations[entity] && onNavigate) {
|
|
108
|
+
onNavigate(entityListDestinations[entity], entity);
|
|
109
|
+
handledByAction = true;
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
// run-client-action
|
|
113
|
+
if (actionType === 'run-client-action' && onClientAction) {
|
|
114
|
+
const actionId = typeof uiAction.actionId === 'string' ? uiAction.actionId : '';
|
|
115
|
+
const payload = isRecord(uiAction.payload) ? uiAction.payload : undefined;
|
|
116
|
+
if (actionId) {
|
|
117
|
+
if (dedupeEnabled && dedupeActionIds.includes(actionId)) {
|
|
118
|
+
const key = `${actionId}:${safeStringify(payload)}`;
|
|
119
|
+
const now = Date.now();
|
|
120
|
+
const lastSeen = recentClientActions.get(key);
|
|
121
|
+
if (lastSeen && now - lastSeen < dedupeWindowMs) {
|
|
122
|
+
handledByAction = true;
|
|
123
|
+
continue;
|
|
124
|
+
}
|
|
125
|
+
recentClientActions.set(key, now);
|
|
126
|
+
}
|
|
127
|
+
onClientAction(actionId, payload);
|
|
128
|
+
handledByAction = true;
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
// Built-in navigate: route through onClientAction so consumers
|
|
132
|
+
// only need useAiClientAction('navigate', ...) — no raw CustomEvent.
|
|
133
|
+
if (actionType === 'navigate' && onClientAction) {
|
|
134
|
+
const path = typeof uiAction.path === 'string' ? uiAction.path : '';
|
|
135
|
+
if (path) {
|
|
136
|
+
if (dedupeEnabled && dedupeActionIds.includes('navigate')) {
|
|
137
|
+
const key = `navigate:${path}`;
|
|
138
|
+
const now = Date.now();
|
|
139
|
+
const lastSeen = recentClientActions.get(key);
|
|
140
|
+
if (lastSeen && now - lastSeen < dedupeWindowMs) {
|
|
141
|
+
handledByAction = true;
|
|
142
|
+
continue;
|
|
143
|
+
}
|
|
144
|
+
recentClientActions.set(key, now);
|
|
145
|
+
}
|
|
146
|
+
onClientAction('navigate', { path });
|
|
147
|
+
handledByAction = true;
|
|
148
|
+
continue; // skip actionHandlers fallback for navigate
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
// Custom action handlers
|
|
152
|
+
if (actionHandlers[actionType]) {
|
|
153
|
+
actionHandlers[actionType](uiAction);
|
|
154
|
+
handledByAction = true;
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
const successful = hasSuccessfulResult(result);
|
|
159
|
+
// Tool-specific fallback when no action was processed
|
|
160
|
+
if (!handledByAction && successful && toolFallbacks[toolName]) {
|
|
161
|
+
toolFallbacks[toolName](result);
|
|
162
|
+
}
|
|
163
|
+
// Global success callback
|
|
164
|
+
if (successful && onSuccess) {
|
|
165
|
+
onSuccess(toolName, result);
|
|
166
|
+
}
|
|
167
|
+
};
|
|
168
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { Mention, MentionData, MentionSerializationConfig } from '../types.js';
|
|
2
|
+
export declare function stripContextBlocks(text: string): string;
|
|
3
|
+
export declare function inferMentionPrefix(type: string): string;
|
|
4
|
+
export declare function normalizeMentionSlug(value: string): string;
|
|
5
|
+
export type MentionTextSegment = {
|
|
6
|
+
kind: 'text';
|
|
7
|
+
text: string;
|
|
8
|
+
} | {
|
|
9
|
+
kind: 'mention';
|
|
10
|
+
mention: Mention;
|
|
11
|
+
};
|
|
12
|
+
export declare function parseMentionSegments(text: string): MentionTextSegment[];
|
|
13
|
+
export declare function serializeMentionsForSend(text: string, mentions: MentionData[], options?: MentionSerializationConfig): string;
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.stripContextBlocks = stripContextBlocks;
|
|
4
|
+
exports.inferMentionPrefix = inferMentionPrefix;
|
|
5
|
+
exports.normalizeMentionSlug = normalizeMentionSlug;
|
|
6
|
+
exports.parseMentionSegments = parseMentionSegments;
|
|
7
|
+
exports.serializeMentionsForSend = serializeMentionsForSend;
|
|
8
|
+
const CONTEXT_BLOCK_RE = /<!--\s*context:[\s\S]*?-->\s*/gi;
|
|
9
|
+
const MENTION_MARKER_RE = /\{\{mention:([^:}]+):([^:}]+):([^}]+)\}\}/g;
|
|
10
|
+
function escapeRegex(value) {
|
|
11
|
+
return value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
12
|
+
}
|
|
13
|
+
function stripContextBlocks(text) {
|
|
14
|
+
return text.replace(CONTEXT_BLOCK_RE, '').trimStart();
|
|
15
|
+
}
|
|
16
|
+
function inferMentionPrefix(type) {
|
|
17
|
+
return type === 'task' || type === 'command' ? '/' : '@';
|
|
18
|
+
}
|
|
19
|
+
function normalizeMentionSlug(value) {
|
|
20
|
+
return value
|
|
21
|
+
.toLowerCase()
|
|
22
|
+
.normalize('NFKD')
|
|
23
|
+
.replace(/[^\w\s-]/g, '')
|
|
24
|
+
.trim()
|
|
25
|
+
.replace(/\s+/g, '-');
|
|
26
|
+
}
|
|
27
|
+
function parseMentionSegments(text) {
|
|
28
|
+
const segments = [];
|
|
29
|
+
let cursor = 0;
|
|
30
|
+
let match;
|
|
31
|
+
MENTION_MARKER_RE.lastIndex = 0;
|
|
32
|
+
while ((match = MENTION_MARKER_RE.exec(text)) !== null) {
|
|
33
|
+
const [raw, type, id, label] = match;
|
|
34
|
+
const start = match.index;
|
|
35
|
+
const end = start + raw.length;
|
|
36
|
+
if (start > cursor) {
|
|
37
|
+
segments.push({ kind: 'text', text: text.slice(cursor, start) });
|
|
38
|
+
}
|
|
39
|
+
segments.push({
|
|
40
|
+
kind: 'mention',
|
|
41
|
+
mention: {
|
|
42
|
+
id,
|
|
43
|
+
type,
|
|
44
|
+
displayName: label,
|
|
45
|
+
prefix: inferMentionPrefix(type),
|
|
46
|
+
},
|
|
47
|
+
});
|
|
48
|
+
cursor = end;
|
|
49
|
+
}
|
|
50
|
+
if (cursor < text.length) {
|
|
51
|
+
segments.push({ kind: 'text', text: text.slice(cursor) });
|
|
52
|
+
}
|
|
53
|
+
return segments;
|
|
54
|
+
}
|
|
55
|
+
function buildContextBlock(mentions, label) {
|
|
56
|
+
if (mentions.length === 0)
|
|
57
|
+
return null;
|
|
58
|
+
const summary = mentions
|
|
59
|
+
.map((m) => {
|
|
60
|
+
const displayName = m.displayName ?? m.label ?? m.id;
|
|
61
|
+
return `${displayName} (ID: ${m.id})`;
|
|
62
|
+
})
|
|
63
|
+
.join(', ');
|
|
64
|
+
return `<!--context:[${label}: ${summary}]--> `;
|
|
65
|
+
}
|
|
66
|
+
function serializeMentionsForSend(text, mentions, options) {
|
|
67
|
+
const enabled = options?.enabled ?? true;
|
|
68
|
+
if (!enabled || mentions.length === 0) {
|
|
69
|
+
return stripContextBlocks(text);
|
|
70
|
+
}
|
|
71
|
+
const cleaned = stripContextBlocks(text);
|
|
72
|
+
let nextText = cleaned;
|
|
73
|
+
const serializedMentions = [];
|
|
74
|
+
for (const mention of mentions) {
|
|
75
|
+
const id = typeof mention.id === 'string' ? mention.id : '';
|
|
76
|
+
const type = typeof mention.type === 'string' ? mention.type : '';
|
|
77
|
+
if (!id || !type)
|
|
78
|
+
continue;
|
|
79
|
+
const label = typeof mention.label === 'string'
|
|
80
|
+
? mention.label
|
|
81
|
+
: typeof mention.displayName === 'string'
|
|
82
|
+
? mention.displayName
|
|
83
|
+
: id;
|
|
84
|
+
const slug = typeof mention.slug === 'string' && mention.slug.trim().length > 0
|
|
85
|
+
? mention.slug.trim()
|
|
86
|
+
: normalizeMentionSlug(label);
|
|
87
|
+
const prefix = typeof mention.prefix === 'string' && mention.prefix.length > 0
|
|
88
|
+
? mention.prefix
|
|
89
|
+
: inferMentionPrefix(type);
|
|
90
|
+
const token = `${prefix}${slug}`;
|
|
91
|
+
const marker = `{{mention:${type}:${id}:${label}}}`;
|
|
92
|
+
const tokenPattern = new RegExp(`(^|\\s)${escapeRegex(token)}(?=\\s|$)`, 'g');
|
|
93
|
+
let wasInserted = false;
|
|
94
|
+
nextText = nextText.replace(tokenPattern, (fullMatch, leading) => {
|
|
95
|
+
wasInserted = true;
|
|
96
|
+
return `${leading}${marker}`;
|
|
97
|
+
});
|
|
98
|
+
if (wasInserted) {
|
|
99
|
+
serializedMentions.push({
|
|
100
|
+
id,
|
|
101
|
+
type,
|
|
102
|
+
label,
|
|
103
|
+
displayName: label,
|
|
104
|
+
slug,
|
|
105
|
+
prefix,
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
if (options?.includeContextBlock) {
|
|
110
|
+
const contextLabel = options.contextLabel ?? 'Referenced entities';
|
|
111
|
+
const contextBlock = buildContextBlock(serializedMentions, contextLabel);
|
|
112
|
+
if (contextBlock) {
|
|
113
|
+
return `${contextBlock}${nextText}`.trim();
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
return nextText;
|
|
117
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { PipelineStepReport } from '../types.js';
|
|
2
|
+
/**
|
|
3
|
+
* Merge incoming pipeline steps with existing stable state.
|
|
4
|
+
*
|
|
5
|
+
* Handles:
|
|
6
|
+
* - Synthetic resume steps (step 0 with stop reason after approval waits)
|
|
7
|
+
* - Timestamp-based conflict resolution for duplicate step numbers
|
|
8
|
+
* - Stable ordering by step number
|
|
9
|
+
*
|
|
10
|
+
* @example
|
|
11
|
+
* ```tsx
|
|
12
|
+
* const [stableSteps, setStableSteps] = useState<PipelineStepReport[]>([]);
|
|
13
|
+
*
|
|
14
|
+
* useEffect(() => {
|
|
15
|
+
* setStableSteps((prev) => mergePipelineSteps(prev, pipelineSteps));
|
|
16
|
+
* }, [pipelineSteps]);
|
|
17
|
+
* ```
|
|
18
|
+
*/
|
|
19
|
+
export declare function mergePipelineSteps(previous: PipelineStepReport[], incoming: PipelineStepReport[]): PipelineStepReport[];
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.mergePipelineSteps = mergePipelineSteps;
|
|
4
|
+
function parseStepTimestamp(value) {
|
|
5
|
+
const ts = Date.parse(value);
|
|
6
|
+
return Number.isFinite(ts) ? ts : null;
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* Merge incoming pipeline steps with existing stable state.
|
|
10
|
+
*
|
|
11
|
+
* Handles:
|
|
12
|
+
* - Synthetic resume steps (step 0 with stop reason after approval waits)
|
|
13
|
+
* - Timestamp-based conflict resolution for duplicate step numbers
|
|
14
|
+
* - Stable ordering by step number
|
|
15
|
+
*
|
|
16
|
+
* @example
|
|
17
|
+
* ```tsx
|
|
18
|
+
* const [stableSteps, setStableSteps] = useState<PipelineStepReport[]>([]);
|
|
19
|
+
*
|
|
20
|
+
* useEffect(() => {
|
|
21
|
+
* setStableSteps((prev) => mergePipelineSteps(prev, pipelineSteps));
|
|
22
|
+
* }, [pipelineSteps]);
|
|
23
|
+
* ```
|
|
24
|
+
*/
|
|
25
|
+
function mergePipelineSteps(previous, incoming) {
|
|
26
|
+
if (incoming.length === 0)
|
|
27
|
+
return [];
|
|
28
|
+
const sorted = [...incoming].sort((a, b) => a.stepNumber - b.stepNumber);
|
|
29
|
+
const previousHasWorkflow = previous.some((step) => step.toolCalls.length > 0 || step.requiresApproval);
|
|
30
|
+
const firstIncoming = sorted[0];
|
|
31
|
+
const isSyntheticResumeStep = firstIncoming &&
|
|
32
|
+
firstIncoming.stepNumber === 0 &&
|
|
33
|
+
firstIncoming.finishReason === 'stop' &&
|
|
34
|
+
firstIncoming.toolCalls.length === 0 &&
|
|
35
|
+
firstIncoming.toolResults === 0 &&
|
|
36
|
+
!firstIncoming.requiresApproval;
|
|
37
|
+
// After an approval wait, the server sends a synthetic step 0 with finishReason='stop'.
|
|
38
|
+
// Instead of replacing previous steps, append it as a final step.
|
|
39
|
+
if (isSyntheticResumeStep && previousHasWorkflow) {
|
|
40
|
+
const resolved = previous.map((step) => step.requiresApproval
|
|
41
|
+
? { ...step, requiresApproval: false, pendingApprovals: 0 }
|
|
42
|
+
: step);
|
|
43
|
+
const alreadyFinalized = resolved.some((step) => step.finishReason === 'stop' && step.toolCalls.length === 0);
|
|
44
|
+
if (alreadyFinalized)
|
|
45
|
+
return resolved;
|
|
46
|
+
return [
|
|
47
|
+
...resolved,
|
|
48
|
+
{ ...firstIncoming, stepNumber: resolved.length },
|
|
49
|
+
];
|
|
50
|
+
}
|
|
51
|
+
// Also clear approval flags when execution continues with tool calls after approval.
|
|
52
|
+
// This handles cases where followup tools (like suggest_followups) execute after
|
|
53
|
+
// approval is granted, instead of getting a synthetic resume step.
|
|
54
|
+
const previousHadApprovals = previous.some((step) => step.requiresApproval);
|
|
55
|
+
const incomingAllCleared = sorted.every((step) => step.pendingApprovals === 0);
|
|
56
|
+
const shouldClearFlags = previousHadApprovals && incomingAllCleared && sorted.length > 0;
|
|
57
|
+
if (shouldClearFlags) {
|
|
58
|
+
previous = previous.map((step) => step.requiresApproval
|
|
59
|
+
? { ...step, requiresApproval: false, pendingApprovals: 0 }
|
|
60
|
+
: step);
|
|
61
|
+
}
|
|
62
|
+
// Standard merge: keep newer timestamps when step numbers collide
|
|
63
|
+
const mergedByStep = new Map();
|
|
64
|
+
for (const step of previous)
|
|
65
|
+
mergedByStep.set(step.stepNumber, step);
|
|
66
|
+
for (const step of sorted) {
|
|
67
|
+
const existing = mergedByStep.get(step.stepNumber);
|
|
68
|
+
if (!existing) {
|
|
69
|
+
mergedByStep.set(step.stepNumber, step);
|
|
70
|
+
continue;
|
|
71
|
+
}
|
|
72
|
+
const existingTs = parseStepTimestamp(existing.createdAt) ?? 0;
|
|
73
|
+
const incomingTs = parseStepTimestamp(step.createdAt) ?? 0;
|
|
74
|
+
if (incomingTs >= existingTs) {
|
|
75
|
+
mergedByStep.set(step.stepNumber, step);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
return Array.from(mergedByStep.entries())
|
|
79
|
+
.sort((a, b) => a[0] - b[0])
|
|
80
|
+
.map(([, step]) => step);
|
|
81
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Sanitize raw stored messages to remove incomplete tool calls that would
|
|
3
|
+
* cause `AI_MissingToolResultsError` when sent to the API.
|
|
4
|
+
*
|
|
5
|
+
* This handles the case where a user closes the chat or refreshes while
|
|
6
|
+
* a tool approval is pending, leaving orphaned tool calls in the history.
|
|
7
|
+
*
|
|
8
|
+
* Call this when loading messages from storage (sessionStorage, localStorage,
|
|
9
|
+
* or any backend) before passing them to `setMessages()` or the SDK provider.
|
|
10
|
+
*
|
|
11
|
+
* @example
|
|
12
|
+
* ```ts
|
|
13
|
+
* const sessionAdapter: SessionConfig = {
|
|
14
|
+
* load: () => {
|
|
15
|
+
* const raw = JSON.parse(sessionStorage.getItem('chat') ?? '[]');
|
|
16
|
+
* return sanitizeStoredMessages(raw);
|
|
17
|
+
* },
|
|
18
|
+
* save: (_, msgs) => sessionStorage.setItem('chat', JSON.stringify(msgs)),
|
|
19
|
+
* clear: () => sessionStorage.removeItem('chat'),
|
|
20
|
+
* };
|
|
21
|
+
* ```
|
|
22
|
+
*/
|
|
23
|
+
export declare function sanitizeStoredMessages(messages: unknown[]): unknown[];
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.sanitizeStoredMessages = sanitizeStoredMessages;
|
|
4
|
+
function isRecord(value) {
|
|
5
|
+
return typeof value === 'object' && value !== null;
|
|
6
|
+
}
|
|
7
|
+
/**
|
|
8
|
+
* Sanitize raw stored messages to remove incomplete tool calls that would
|
|
9
|
+
* cause `AI_MissingToolResultsError` when sent to the API.
|
|
10
|
+
*
|
|
11
|
+
* This handles the case where a user closes the chat or refreshes while
|
|
12
|
+
* a tool approval is pending, leaving orphaned tool calls in the history.
|
|
13
|
+
*
|
|
14
|
+
* Call this when loading messages from storage (sessionStorage, localStorage,
|
|
15
|
+
* or any backend) before passing them to `setMessages()` or the SDK provider.
|
|
16
|
+
*
|
|
17
|
+
* @example
|
|
18
|
+
* ```ts
|
|
19
|
+
* const sessionAdapter: SessionConfig = {
|
|
20
|
+
* load: () => {
|
|
21
|
+
* const raw = JSON.parse(sessionStorage.getItem('chat') ?? '[]');
|
|
22
|
+
* return sanitizeStoredMessages(raw);
|
|
23
|
+
* },
|
|
24
|
+
* save: (_, msgs) => sessionStorage.setItem('chat', JSON.stringify(msgs)),
|
|
25
|
+
* clear: () => sessionStorage.removeItem('chat'),
|
|
26
|
+
* };
|
|
27
|
+
* ```
|
|
28
|
+
*/
|
|
29
|
+
function sanitizeStoredMessages(messages) {
|
|
30
|
+
return messages
|
|
31
|
+
.map((message) => {
|
|
32
|
+
if (!isRecord(message))
|
|
33
|
+
return message;
|
|
34
|
+
const parts = Array.isArray(message.parts) ? message.parts : [];
|
|
35
|
+
const sanitizedParts = parts.filter((part) => {
|
|
36
|
+
if (!isRecord(part))
|
|
37
|
+
return true;
|
|
38
|
+
const partType = typeof part.type === 'string' ? part.type : '';
|
|
39
|
+
// Remove tool parts stuck in approval-requested state
|
|
40
|
+
if (partType.startsWith('tool-') && part.state === 'approval-requested') {
|
|
41
|
+
return false;
|
|
42
|
+
}
|
|
43
|
+
return true;
|
|
44
|
+
});
|
|
45
|
+
// If all parts were removed, mark for filtering
|
|
46
|
+
if (sanitizedParts.length === 0 && parts.length > 0) {
|
|
47
|
+
return null;
|
|
48
|
+
}
|
|
49
|
+
return { ...message, parts: sanitizedParts };
|
|
50
|
+
})
|
|
51
|
+
.filter((message) => message !== null);
|
|
52
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { CanActivate, ExecutionContext } from '@nestjs/common';
|
|
2
|
+
import { ModuleRef } from '@nestjs/core';
|
|
3
|
+
import { type AiChatModuleOptions } from './ai-chat-module-options.interface.js';
|
|
4
|
+
export declare class AiChatAuthGuard implements CanActivate {
|
|
5
|
+
private readonly options;
|
|
6
|
+
private readonly moduleRef;
|
|
7
|
+
private guardInstance;
|
|
8
|
+
constructor(options: AiChatModuleOptions | null, moduleRef: ModuleRef);
|
|
9
|
+
canActivate(context: ExecutionContext): Promise<boolean>;
|
|
10
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
|
3
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
4
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
5
|
+
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
6
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
7
|
+
};
|
|
8
|
+
var __metadata = (this && this.__metadata) || function (k, v) {
|
|
9
|
+
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
10
|
+
};
|
|
11
|
+
var __param = (this && this.__param) || function (paramIndex, decorator) {
|
|
12
|
+
return function (target, key) { decorator(target, key, paramIndex); }
|
|
13
|
+
};
|
|
14
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
15
|
+
exports.AiChatAuthGuard = void 0;
|
|
16
|
+
const common_1 = require("@nestjs/common");
|
|
17
|
+
const core_1 = require("@nestjs/core");
|
|
18
|
+
const ai_chat_module_options_interface_js_1 = require("./ai-chat-module-options.interface.js");
|
|
19
|
+
let AiChatAuthGuard = class AiChatAuthGuard {
|
|
20
|
+
options;
|
|
21
|
+
moduleRef;
|
|
22
|
+
guardInstance = null;
|
|
23
|
+
constructor(options, moduleRef) {
|
|
24
|
+
this.options = options;
|
|
25
|
+
this.moduleRef = moduleRef;
|
|
26
|
+
}
|
|
27
|
+
async canActivate(context) {
|
|
28
|
+
if (!this.options) {
|
|
29
|
+
return false;
|
|
30
|
+
}
|
|
31
|
+
if (!this.guardInstance) {
|
|
32
|
+
this.guardInstance = await this.moduleRef.create(this.options.authGuard);
|
|
33
|
+
}
|
|
34
|
+
return this.guardInstance.canActivate(context);
|
|
35
|
+
}
|
|
36
|
+
};
|
|
37
|
+
exports.AiChatAuthGuard = AiChatAuthGuard;
|
|
38
|
+
exports.AiChatAuthGuard = AiChatAuthGuard = __decorate([
|
|
39
|
+
(0, common_1.Injectable)(),
|
|
40
|
+
__param(0, (0, common_1.Inject)(ai_chat_module_options_interface_js_1.AI_CHAT_MODULE_OPTIONS)),
|
|
41
|
+
__metadata("design:paramtypes", [Object, core_1.ModuleRef])
|
|
42
|
+
], AiChatAuthGuard);
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import type { CanActivate, InjectionToken, ModuleMetadata, OptionalFactoryDependency, Type } from '@nestjs/common';
|
|
2
|
+
import type { LanguageModel } from 'ai';
|
|
3
|
+
import type { AuditEmitter } from '../../../../types.js';
|
|
4
|
+
import type { Lancer } from '../../../lancer/index.js';
|
|
5
|
+
export declare const AI_CHAT_MODULE_OPTIONS: unique symbol;
|
|
6
|
+
export interface AiChatModuleFeatures {
|
|
7
|
+
speechToText?: boolean;
|
|
8
|
+
}
|
|
9
|
+
export interface AiChatModuleOptions {
|
|
10
|
+
/** Vercel AI SDK LanguageModel — consumer creates and owns this */
|
|
11
|
+
model: LanguageModel;
|
|
12
|
+
/** Project-specific system prompt, static string or factory */
|
|
13
|
+
systemPrompt: string | ((context: {
|
|
14
|
+
currentDate: string;
|
|
15
|
+
}) => string);
|
|
16
|
+
/** NestJS guard class for HTTP authentication */
|
|
17
|
+
authGuard: Type<CanActivate>;
|
|
18
|
+
/** Optional feature toggles */
|
|
19
|
+
features?: AiChatModuleFeatures;
|
|
20
|
+
/** Max tool-call steps per chat request (default: 4) */
|
|
21
|
+
maxSteps?: number;
|
|
22
|
+
/** Unified audit event callback for compliance logging */
|
|
23
|
+
onAudit?: AuditEmitter;
|
|
24
|
+
/** Lancer client for runtime prompt overrides (Pro feature) */
|
|
25
|
+
lancer?: Lancer;
|
|
26
|
+
}
|
|
27
|
+
export interface AiChatModuleAsyncOptions extends Pick<ModuleMetadata, 'imports'> {
|
|
28
|
+
/** Factory that returns options or null to disable the module */
|
|
29
|
+
useFactory: (...args: any[]) => Promise<AiChatModuleOptions | null> | AiChatModuleOptions | null;
|
|
30
|
+
/** Injection tokens to pass to useFactory */
|
|
31
|
+
inject?: (InjectionToken | OptionalFactoryDependency)[];
|
|
32
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { Request, Response } from 'express';
|
|
2
|
+
import { AiChatService } from './ai-chat.service.js';
|
|
3
|
+
import { AiChatRequestDto } from './dto/index.js';
|
|
4
|
+
import { type AiChatModuleOptions } from './ai-chat-module-options.interface.js';
|
|
5
|
+
export declare class AiChatController {
|
|
6
|
+
private readonly options;
|
|
7
|
+
private readonly aiChatService;
|
|
8
|
+
private readonly logger;
|
|
9
|
+
constructor(options: AiChatModuleOptions | null, aiChatService: AiChatService);
|
|
10
|
+
chat(dto: AiChatRequestDto, req: Request, res: Response): Promise<void>;
|
|
11
|
+
}
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
|
3
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
4
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
5
|
+
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
6
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
7
|
+
};
|
|
8
|
+
var __metadata = (this && this.__metadata) || function (k, v) {
|
|
9
|
+
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
10
|
+
};
|
|
11
|
+
var __param = (this && this.__param) || function (paramIndex, decorator) {
|
|
12
|
+
return function (target, key) { decorator(target, key, paramIndex); }
|
|
13
|
+
};
|
|
14
|
+
var AiChatController_1;
|
|
15
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
16
|
+
exports.AiChatController = void 0;
|
|
17
|
+
const common_1 = require("@nestjs/common");
|
|
18
|
+
const swagger_1 = require("@nestjs/swagger");
|
|
19
|
+
const ai_chat_service_js_1 = require("./ai-chat.service.js");
|
|
20
|
+
const index_js_1 = require("./dto/index.js");
|
|
21
|
+
const ai_chat_module_options_interface_js_1 = require("./ai-chat-module-options.interface.js");
|
|
22
|
+
const ai_chat_auth_guard_js_1 = require("./ai-chat-auth.guard.js");
|
|
23
|
+
let AiChatController = AiChatController_1 = class AiChatController {
|
|
24
|
+
options;
|
|
25
|
+
aiChatService;
|
|
26
|
+
logger = new common_1.Logger(AiChatController_1.name);
|
|
27
|
+
constructor(options, aiChatService) {
|
|
28
|
+
this.options = options;
|
|
29
|
+
this.aiChatService = aiChatService;
|
|
30
|
+
}
|
|
31
|
+
async chat(dto, req, res) {
|
|
32
|
+
if (!this.options) {
|
|
33
|
+
throw new common_1.ServiceUnavailableException('AI chat is not configured');
|
|
34
|
+
}
|
|
35
|
+
const authHeader = req.headers.authorization ?? '';
|
|
36
|
+
this.logger.debug(`Chat request: ${dto.messages.length} messages`);
|
|
37
|
+
let streamResponse;
|
|
38
|
+
try {
|
|
39
|
+
streamResponse = await this.aiChatService.streamChat(dto.messages, authHeader);
|
|
40
|
+
}
|
|
41
|
+
catch (error) {
|
|
42
|
+
this.logger.error(`streamChat() threw: ${error}`, error.stack);
|
|
43
|
+
throw error;
|
|
44
|
+
}
|
|
45
|
+
this.logger.debug(`Stream response status: ${streamResponse.status}`);
|
|
46
|
+
res.status(streamResponse.status);
|
|
47
|
+
streamResponse.headers.forEach((value, key) => {
|
|
48
|
+
res.setHeader(key, value);
|
|
49
|
+
});
|
|
50
|
+
if (streamResponse.body) {
|
|
51
|
+
const reader = streamResponse.body.getReader();
|
|
52
|
+
while (true) {
|
|
53
|
+
const { done, value } = await reader.read();
|
|
54
|
+
if (done)
|
|
55
|
+
break;
|
|
56
|
+
res.write(value);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
this.logger.debug('Stream completed');
|
|
60
|
+
res.end();
|
|
61
|
+
}
|
|
62
|
+
};
|
|
63
|
+
exports.AiChatController = AiChatController;
|
|
64
|
+
__decorate([
|
|
65
|
+
(0, common_1.Post)('chat'),
|
|
66
|
+
(0, swagger_1.ApiOperation)({ summary: 'Stream AI chat response' }),
|
|
67
|
+
__param(0, (0, common_1.Body)()),
|
|
68
|
+
__param(1, (0, common_1.Req)()),
|
|
69
|
+
__param(2, (0, common_1.Res)()),
|
|
70
|
+
__metadata("design:type", Function),
|
|
71
|
+
__metadata("design:paramtypes", [index_js_1.AiChatRequestDto, Object, Object]),
|
|
72
|
+
__metadata("design:returntype", Promise)
|
|
73
|
+
], AiChatController.prototype, "chat", null);
|
|
74
|
+
exports.AiChatController = AiChatController = AiChatController_1 = __decorate([
|
|
75
|
+
(0, swagger_1.ApiTags)('ai'),
|
|
76
|
+
(0, common_1.UseGuards)(ai_chat_auth_guard_js_1.AiChatAuthGuard),
|
|
77
|
+
(0, common_1.Controller)('ai'),
|
|
78
|
+
__param(0, (0, common_1.Inject)(ai_chat_module_options_interface_js_1.AI_CHAT_MODULE_OPTIONS)),
|
|
79
|
+
__metadata("design:paramtypes", [Object, ai_chat_service_js_1.AiChatService])
|
|
80
|
+
], AiChatController);
|