@k2b/cloud 0.6.0 → 0.7.0
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/README.md +2 -2
- package/package.json +19 -9
- package/scripts/browser-performance.ts +14 -0
- package/scripts/build-pdf-renderer.ts +39 -0
- package/scripts/build.ts +15 -35
- package/scripts/preload.ts +9 -12
- package/scripts/sync-recovery-smoke.ts +2 -1
- package/src/_internal/app-presentation.ts +14 -2
- package/src/_internal/capabilities.ts +108 -7
- package/src/_internal/capability-streams.ts +81 -0
- package/src/_internal/define-app.ts +62 -36
- package/src/_internal/heartbeat.ts +4 -0
- package/src/_internal/help-catalog.ts +27 -172
- package/src/_internal/help.ts +13 -76
- package/src/_internal/page-responses.ts +2 -0
- package/src/_internal/postgres-application-name.ts +30 -0
- package/src/_internal/process-identity.ts +15 -0
- package/src/_internal/process-sync.ts +1 -1
- package/src/_internal/registry-validation.ts +29 -25
- package/src/_internal/registry.ts +2 -53
- package/src/_internal/runtime-context.ts +5 -16
- package/src/_internal/server-timing.ts +13 -0
- package/src/_internal/status-preserving-ssr.ts +18 -6
- package/src/_internal/web-vitals-asset.ts +10 -0
- package/src/access/PermissionEditor.tsx +14 -153
- package/src/access/PrincipalPicker.tsx +104 -0
- package/src/access/ui.ts +2 -0
- package/src/ai/admin.ts +14 -13
- package/src/ai/assistant-models.ts +12 -1
- package/src/ai/audio-format.ts +34 -0
- package/src/ai/audio-tool.ts +90 -0
- package/src/ai/browser-code-contracts.ts +79 -0
- package/src/ai/browser.ts +2 -0
- package/src/ai/capabilities.ts +55 -36
- package/src/ai/capability-execution.ts +47 -12
- package/src/ai/chat/blocks.tsx +157 -95
- package/src/ai/chat/capability-result.ts +15 -0
- package/src/ai/chat/capability-table.tsx +2 -10
- package/src/ai/chat/composer-adapter.ts +48 -3
- package/src/ai/chat/message-actions.tsx +8 -7
- package/src/ai/chat/message-utils.ts +12 -1
- package/src/ai/chat/messages.ts +2 -0
- package/src/ai/chat/presentation.tsx +19 -48
- package/src/ai/chat/primitives.tsx +43 -1
- package/src/ai/chat/tool-disclosure.tsx +17 -2
- package/src/ai/chat/tool-groups.ts +52 -0
- package/src/ai/chat-quotas.ts +25 -0
- package/src/ai/chat-task-contracts.ts +9 -1
- package/src/ai/chat-tasks.ts +276 -43
- package/src/ai/client/controller.ts +110 -33
- package/src/ai/client/file-source.ts +12 -6
- package/src/ai/client/live-connection.ts +1 -0
- package/src/ai/client/projection.ts +22 -11
- package/src/ai/client/transport.ts +4 -0
- package/src/ai/code-capability-routes.ts +110 -0
- package/src/ai/code-capability-transport.ts +38 -0
- package/src/ai/code-mode-skill.ts +114 -0
- package/src/ai/code-runtime-tools.ts +166 -0
- package/src/ai/code-source-contracts.ts +202 -0
- package/src/ai/code-source-tools.ts +145 -0
- package/src/ai/compaction.ts +27 -6
- package/src/ai/data-analysis-skill.ts +10 -0
- package/src/ai/default-tools.ts +25 -2
- package/src/ai/dictation-runtime.ts +171 -0
- package/src/ai/dictations.ts +188 -0
- package/src/ai/draft-content.ts +34 -0
- package/src/ai/enrich.ts +6 -0
- package/src/ai/executor.ts +191 -31
- package/src/ai/file-content-version.ts +22 -0
- package/src/ai/file-context.ts +2 -1
- package/src/ai/file-media-type.ts +12 -0
- package/src/ai/file-reference-contracts.ts +15 -0
- package/src/ai/file-tools.ts +2 -1
- package/src/ai/files-store.ts +174 -71
- package/src/ai/fixtures/vision-encrypted.pdf +0 -0
- package/src/ai/grids-skill.ts +40 -10
- package/src/ai/http.ts +15 -8
- package/src/ai/index.ts +26 -2
- package/src/ai/inference-calls.ts +198 -0
- package/src/ai/live-events.ts +5 -1
- package/src/ai/memory-learning.ts +60 -28
- package/src/ai/message-queue.ts +167 -0
- package/src/ai/migrate.ts +187 -11
- package/src/ai/model-pricing.ts +43 -0
- package/src/ai/pdf-render-worker.ts +74 -0
- package/src/ai/pdf-render.fixture.ts +28 -0
- package/src/ai/pdf-render.ts +77 -0
- package/src/ai/projects-routes.ts +3 -3
- package/src/ai/projects.ts +20 -6
- package/src/ai/protocol.ts +3 -0
- package/src/ai/provider.ts +1 -8
- package/src/ai/quota-provider.ts +191 -0
- package/src/ai/quota-report.ts +152 -0
- package/src/ai/quotas-migrate.ts +45 -0
- package/src/ai/quotas.ts +168 -0
- package/src/ai/routes.ts +215 -11
- package/src/ai/run-timeout.ts +10 -0
- package/src/ai/runtime-tools.ts +3 -0
- package/src/ai/runtime.ts +55 -16
- package/src/ai/settings.ts +36 -8
- package/src/ai/short-id.ts +2 -2
- package/src/ai/skill-catalog.ts +0 -10
- package/src/ai/skill-search.ts +47 -0
- package/src/ai/skill-seeds.ts +105 -20
- package/src/ai/skill-tool.ts +16 -5
- package/src/ai/skills-routes.ts +32 -4
- package/src/ai/skills.ts +324 -155
- package/src/ai/store.ts +373 -191
- package/src/ai/stream.ts +5 -0
- package/src/ai/structured-runs.ts +4 -3
- package/src/ai/structured.ts +8 -1
- package/src/ai/system-prompt.ts +12 -2
- package/src/ai/timeline.ts +2 -5
- package/src/ai/todo-contracts.ts +20 -0
- package/src/ai/todo-tool.ts +16 -0
- package/src/ai/tool-audit.ts +30 -40
- package/src/ai/tools.ts +6 -0
- package/src/ai/transcription.ts +202 -0
- package/src/ai/turn-timing.ts +114 -0
- package/src/ai/types.ts +58 -10
- package/src/ai/ui.tsx +2 -0
- package/src/ai/usage.ts +118 -118
- package/src/ai/vision-tool.ts +39 -10
- package/src/api/admin-ai-projects.ts +2 -2
- package/src/api/admin-ai-quotas.ts +74 -0
- package/src/api/admin-ai-skills.ts +48 -5
- package/src/api/admin-ai-usage.ts +2 -2
- package/src/api/admin-core-settings.ts +25 -0
- package/src/api/admin-identity.ts +91 -51
- package/src/api/admin-rail.ts +58 -0
- package/src/api/announcements.ts +14 -0
- package/src/api/capabilities.ts +292 -43
- package/src/api/capability-streams.ts +262 -0
- package/src/api/help.ts +8 -44
- package/src/api/index.ts +6 -0
- package/src/api/mcp.ts +44 -26
- package/src/api/me-web-vitals.ts +35 -0
- package/src/api/me.ts +2 -0
- package/src/api/search/schemas.ts +26 -15
- package/src/api/search.bench.ts +1 -1
- package/src/api/search.ts +54 -17
- package/src/browser/CloudResourceSearch.tsx +640 -702
- package/src/browser/app-approval-vault.ts +19 -7
- package/src/browser/command-bridge.ts +109 -0
- package/src/browser/command-shortcuts.ts +76 -0
- package/src/browser/commands.ts +66 -0
- package/src/browser/navigation-search.ts +48 -0
- package/src/browser/notifications.ts +1 -1
- package/src/browser/resource-picker.tsx +5 -18
- package/src/browser/resource-search-dialog.ts +9 -0
- package/src/browser/resource-search-input.ts +46 -0
- package/src/browser/resource-search-messages.ts +76 -40
- package/src/browser/resource-search.ts +7 -0
- package/src/browser/search-bridge.ts +102 -0
- package/src/browser/search-commands.ts +59 -0
- package/src/browser/search.ts +13 -0
- package/src/browser/spotlight-position.ts +208 -0
- package/src/browser/testing.ts +5 -0
- package/src/browser/web-vitals.ts +25 -0
- package/src/capabilities/claims.ts +179 -0
- package/src/capabilities/client.ts +34 -0
- package/src/capabilities/command-link.ts +16 -0
- package/src/capabilities/executions.ts +298 -0
- package/src/capabilities/migrate.ts +81 -0
- package/src/capabilities/server.ts +114 -34
- package/src/capabilities/stream-body.ts +16 -0
- package/src/capabilities/streams.ts +28 -0
- package/src/cli/admin/ai-quotas.ts +161 -0
- package/src/cli/admin/ai-skills.ts +64 -0
- package/src/cli/admin/ai-usage.ts +53 -30
- package/src/cli/admin/app-credentials.ts +79 -0
- package/src/cli/admin/data.ts +17 -0
- package/src/cli/admin/index.ts +9 -0
- package/src/cli/capabilities.ts +29 -0
- package/src/cli/capability-streams.ts +25 -0
- package/src/cli/index.ts +3 -2
- package/src/config/env.ts +11 -8
- package/src/contracts/app.ts +23 -3
- package/src/contracts/capabilities.ts +68 -5
- package/src/contracts/capability-streams.ts +46 -0
- package/src/contracts/commands.ts +49 -0
- package/src/contracts/index.ts +6 -1
- package/src/contracts/rail-admin.ts +38 -0
- package/src/contracts/rail-preferences.ts +16 -0
- package/src/contracts/registry.ts +3 -27
- package/src/contracts/settings-types.ts +1 -1
- package/src/contracts/shared.ts +9 -5
- package/src/contracts/web-vitals.ts +27 -0
- package/src/index.ts +0 -4
- package/src/server/index.ts +3 -0
- package/src/server/middleware/auth.ts +3 -2
- package/src/server/middleware/route-template.ts +9 -3
- package/src/server/middleware/runtime.ts +2 -1
- package/src/server/middleware/settings.ts +17 -11
- package/src/server/services/access-revision.ts +10 -0
- package/src/server/services/access.ts +1 -1
- package/src/server/services/index.ts +1 -0
- package/src/services/account-category-policy.ts +9 -0
- package/src/services/account-lifecycle/audit.ts +1 -1
- package/src/services/accounts/app.ts +12 -2
- package/src/services/accounts/email-write.ts +23 -0
- package/src/services/accounts/groups.ts +15 -4
- package/src/services/accounts/identities.ts +249 -0
- package/src/services/accounts/identity-reconciliation.ts +87 -0
- package/src/services/accounts/ipa-data.ts +2 -2
- package/src/services/accounts/local-groups.ts +2 -2
- package/src/services/accounts/posix.ts +45 -29
- package/src/services/accounts/users.ts +2 -2
- package/src/services/announcements/index.ts +69 -8
- package/src/services/audit/index.ts +1 -1
- package/src/services/cache-fill.ts +35 -0
- package/src/services/help/index.ts +109 -0
- package/src/services/help/maintenance.ts +25 -0
- package/src/services/help/store.ts +180 -0
- package/src/services/help/types.ts +46 -0
- package/src/services/identity/invocation-actor.ts +2 -2
- package/src/services/identity/key-config.ts +2 -1
- package/src/services/identity/key-ring.ts +1 -1
- package/src/services/index.ts +15 -0
- package/src/services/ipa/users.ts +268 -221
- package/src/services/legal-consent.ts +1 -1
- package/src/services/logging/index.ts +15 -13
- package/src/services/logging/trace.ts +2 -1
- package/src/services/logging/web-vitals.ts +81 -0
- package/src/services/mandates/index.ts +18 -4
- package/src/services/mandates/policy.ts +65 -2
- package/src/services/notifications/batches.ts +124 -57
- package/src/services/notifications/browser.ts +12 -0
- package/src/services/notifications/index.ts +0 -2
- package/src/services/notifications/platform.ts +0 -4
- package/src/services/oauth-tokens.ts +3 -3
- package/src/services/pdf/gotenberg.ts +107 -84
- package/src/services/pdf/index.ts +3 -0
- package/src/services/providers/local/users.ts +25 -16
- package/src/services/public-http.ts +101 -0
- package/src/services/rail-shortcuts.ts +95 -0
- package/src/services/rail-snapshot.ts +53 -0
- package/src/services/request-cache-redis.ts +28 -0
- package/src/services/session/index.ts +15 -2
- package/src/services/session/user.ts +62 -37
- package/src/services/settings/core-settings.ts +21 -28
- package/src/services/settings/defaults.ts +5 -0
- package/src/services/settings/store.ts +58 -84
- package/src/shared/ai-costs.ts +42 -0
- package/src/shared/ai-platform-prompt.ts +1 -1
- package/src/shared/ai-quotas.ts +136 -0
- package/src/shared/ai-usage.ts +6 -16
- package/src/shared/app-presentation.ts +21 -15
- package/src/shared/capability-messages.ts +14 -0
- package/src/shared/help.ts +6 -42
- package/src/shared/index.ts +6 -0
- package/src/ssr/AdminLayout.tsx +7 -5
- package/src/ssr/AdminSidebar.tsx +29 -15
- package/src/ssr/AppLaunchpad.island.tsx +24 -137
- package/src/ssr/AppLaunchpadPanel.tsx +181 -0
- package/src/ssr/BrowserPushRegistration.island.tsx +12 -0
- package/src/ssr/GlobalSearchDialog.tsx +167 -20
- package/src/ssr/GlobalSearchTrigger.island.tsx +33 -22
- package/src/ssr/Layout.tsx +17 -6
- package/src/ssr/LayoutBreadcrumbs.island.tsx +3 -5
- package/src/ssr/LayoutFooter.tsx +1 -1
- package/src/ssr/LayoutHeader.tsx +46 -14
- package/src/ssr/LayoutHelp.tsx +23 -22
- package/src/ssr/{HotkeysHelpRail.island.tsx → LayoutHelpTrigger.island.tsx} +18 -12
- package/src/ssr/LayoutRail.tsx +17 -9
- package/src/ssr/MobileNavigation.tsx +71 -0
- package/src/ssr/MobileProfileActions.tsx +49 -0
- package/src/ssr/ProfilePreferences.island.tsx +3 -36
- package/src/ssr/RailApps.island.tsx +2 -2
- package/src/ssr/RailEditor.tsx +17 -2
- package/src/ssr/WorkspaceNavigation.island.tsx +7 -0
- package/src/ssr/WorkspaceNavigationProvider.tsx +16 -0
- package/src/ssr/admin-navigation.ts +7 -1
- package/src/ssr/help.ts +29 -0
- package/src/ssr/islands/SearchBar.tsx +67 -0
- package/src/ssr/islands/index.ts +3 -1
- package/src/ssr/layout-context.ts +2 -2
- package/src/ssr/layout-help-search.ts +15 -0
- package/src/ssr/mobile-menu-history.ts +70 -0
- package/src/ssr/platform-messages.ts +16 -8
- package/src/ssr/profile-actions.ts +42 -0
- package/src/ssr/rail-context.ts +8 -4
- package/src/ssr/rail-messages.ts +2 -0
- package/src/ssr/rail-navigation.ts +3 -3
- package/src/ssr/workspace-navigation.ts +75 -0
- package/src/styles/effects.css +6 -6
- package/src/styles/global.css +54 -0
- package/src/styles/input.css +43 -41
- package/src/styles/resource-search.css +505 -0
- package/src/styles/utilities-feedback.css +13 -0
- package/src/styles/utilities-layout.css +1 -5
- package/src/styles/utilities-navigation.css +3 -39
- package/src/types/ambient.d.ts +5 -0
- package/src/workflows/ai/runtime.ts +5 -1
- package/src/workflows/ai/store.ts +2 -0
- package/src/workflows/ai-actions.ts +16 -13
- package/src/workflows/definition.ts +10 -5
- package/src/workflows/store/actions.ts +44 -20
- package/src/workflows/store/index.ts +2 -0
- package/src/workflows/store/runs.ts +36 -0
- package/src/workflows/store/worker-pool.ts +55 -0
- package/src/workflows/store/worker-runtime.ts +90 -0
- package/src/ai/kit-skill.ts +0 -22
- package/src/ai/usage-migrate.ts +0 -81
- package/src/browser/notification-target.ts +0 -27
- package/src/contracts/notification-live.ts +0 -66
- package/src/services/notifications/live.ts +0 -50
- package/src/ssr/BrowserNotifications.island.tsx +0 -135
- package/src/ssr/islands/SearchBar.island.tsx +0 -80
package/src/ai/capabilities.ts
CHANGED
|
@@ -1,13 +1,10 @@
|
|
|
1
|
+
import { createHelpReader, type HelpReaderFactory } from "../services/help";
|
|
2
|
+
import { createCodeSourceTool } from "./code-source-tools";
|
|
3
|
+
import { CODE_SOURCE_TOOLS } from "./code-source-contracts";
|
|
1
4
|
import { createHash } from "node:crypto";
|
|
2
5
|
import type { Tool, ToolContext, ToolResolver } from "@k2b/nessi";
|
|
3
6
|
import { z } from "zod";
|
|
4
|
-
import {
|
|
5
|
-
createHelpCatalog,
|
|
6
|
-
HELP_READ_MAX_CHARS,
|
|
7
|
-
HELP_SEARCH_MAX_LIMIT,
|
|
8
|
-
readHelpCatalog,
|
|
9
|
-
searchHelpCatalog,
|
|
10
|
-
} from "../_internal/help-catalog";
|
|
7
|
+
import { HELP_READ_MAX_CHARS, HELP_SEARCH_MAX_LIMIT, readHelpArticle } from "../_internal/help-catalog";
|
|
11
8
|
import {
|
|
12
9
|
type CapabilityActionManifest,
|
|
13
10
|
type CapabilityActionReview,
|
|
@@ -16,8 +13,9 @@ import {
|
|
|
16
13
|
cloudResourceRefAppId,
|
|
17
14
|
resolveCapabilityResourceReader,
|
|
18
15
|
} from "../contracts/capabilities";
|
|
19
|
-
import type { CapabilityRegistryEntry
|
|
16
|
+
import type { CapabilityRegistryEntry } from "../contracts/registry";
|
|
20
17
|
import type { RequestActor } from "../server";
|
|
18
|
+
import { recordRejectedAiCapability } from "./capability-execution";
|
|
21
19
|
import { CLOUD_AI_DEFERRED_BUILTIN_TOOL_NAMES } from "./default-tools";
|
|
22
20
|
import { type AiToolPreparationContext, defineAiTool, type PreparedAiTools, prepareAiTools } from "./tools";
|
|
23
21
|
import type { AiConversationService, AiRuntimeTool, AiToolPresentation } from "./types";
|
|
@@ -304,14 +302,14 @@ const AiHelpDocumentSchema = AiHelpCatalogItemSchema.extend({
|
|
|
304
302
|
truncated: z.boolean(),
|
|
305
303
|
}).strict();
|
|
306
304
|
|
|
307
|
-
/** Search and read
|
|
308
|
-
export const createAiHelpTools = (
|
|
309
|
-
const
|
|
305
|
+
/** Search and read published Help without loading one tool per article. */
|
|
306
|
+
export const createAiHelpTools = (help: HelpReaderFactory = createHelpReader, locale = "en"): AiRuntimeTool[] => {
|
|
307
|
+
const reader = help(locale);
|
|
310
308
|
|
|
311
309
|
const search = defineAiTool({
|
|
312
310
|
name: "search_help",
|
|
313
311
|
description:
|
|
314
|
-
"Search installed Cloud app Help when product behavior, settings, workflows, permissions, or app errors are unclear. Use
|
|
312
|
+
"Search installed Cloud app Help when product behavior, settings, workflows, permissions, or app errors are unclear. Use concise product terms in the request language and scope appId when known. Returns compact document ids for read_help; skip this tool for straightforward live-data requests.",
|
|
315
313
|
inputSchema: z
|
|
316
314
|
.object({
|
|
317
315
|
query: z.string().trim().min(1).max(200).describe("Product task or concept to find."),
|
|
@@ -322,7 +320,7 @@ export const createAiHelpTools = (registry: readonly HelpRegistryEntry[], locale
|
|
|
322
320
|
outputSchema: z.object({ documents: z.array(AiHelpCatalogItemSchema).max(HELP_SEARCH_MAX_LIMIT) }).strict(),
|
|
323
321
|
approval: "never",
|
|
324
322
|
}).server(async ({ query, appId, limit }) => ({
|
|
325
|
-
documents:
|
|
323
|
+
documents: await reader.search({ query, appId, limit: boundedLimit(limit, DEFAULT_SEARCH_LIMIT, HELP_SEARCH_MAX_LIMIT) }),
|
|
326
324
|
}));
|
|
327
325
|
|
|
328
326
|
const read = defineAiTool({
|
|
@@ -338,25 +336,14 @@ export const createAiHelpTools = (registry: readonly HelpRegistryEntry[], locale
|
|
|
338
336
|
.strict(),
|
|
339
337
|
outputSchema: z.object({ document: AiHelpDocumentSchema.nullable() }).strict(),
|
|
340
338
|
approval: "never",
|
|
341
|
-
}).server(async ({ appId, documentId, query }) =>
|
|
342
|
-
document
|
|
343
|
-
|
|
339
|
+
}).server(async ({ appId, documentId, query }) => {
|
|
340
|
+
const document = await reader.read({ appId, documentId });
|
|
341
|
+
return { document: document ? readHelpArticle(document, query) : null };
|
|
342
|
+
});
|
|
344
343
|
|
|
345
344
|
return [search, read];
|
|
346
345
|
};
|
|
347
346
|
|
|
348
|
-
const resolveHelpRegistry = async (
|
|
349
|
-
listRegistry: () => Promise<HelpRegistryEntry[]>,
|
|
350
|
-
onError?: (error: unknown) => void,
|
|
351
|
-
): Promise<HelpRegistryEntry[]> => {
|
|
352
|
-
try {
|
|
353
|
-
return await listRegistry();
|
|
354
|
-
} catch (error) {
|
|
355
|
-
onError?.(error);
|
|
356
|
-
return [];
|
|
357
|
-
}
|
|
358
|
-
};
|
|
359
|
-
|
|
360
347
|
const resolveCapabilityRegistry = async (
|
|
361
348
|
listRegistry: () => Promise<CapabilityRegistryEntry[]>,
|
|
362
349
|
onError?: (error: unknown) => void,
|
|
@@ -554,7 +541,9 @@ export const createAiToolMetaTools = (input: {
|
|
|
554
541
|
export const createLoadedAiCapabilityTools = (input: {
|
|
555
542
|
catalog: readonly AiCapabilityCatalogEntry[];
|
|
556
543
|
loadedNames: readonly string[];
|
|
544
|
+
actor: RequestActor;
|
|
557
545
|
review?: (entry: AiCapabilityCatalogEntry, args: unknown, context: ToolContext) => Promise<CapabilityActionReview | null>;
|
|
546
|
+
authorizeBackground?: (entry: AiCapabilityCatalogEntry, args: unknown) => Promise<void>;
|
|
558
547
|
onReview?: (callId: string, review: CapabilityActionReview) => void;
|
|
559
548
|
execute: (entry: AiCapabilityCatalogEntry, args: unknown, context: ToolContext) => Promise<unknown>;
|
|
560
549
|
}): AiRuntimeTool[] => {
|
|
@@ -573,12 +562,18 @@ export const createLoadedAiCapabilityTools = (input: {
|
|
|
573
562
|
// live review has resolved. The review may supply an app-owned scope.
|
|
574
563
|
approval: "never",
|
|
575
564
|
}).server(async (args, context) => {
|
|
576
|
-
|
|
565
|
+
await input.authorizeBackground?.(entry, args);
|
|
566
|
+
if (!input.authorizeBackground && entry.kind === "action" && (entry.operation as CapabilityActionManifest).approval !== "none") {
|
|
577
567
|
const review = (await input.review?.(entry, args, context)) ?? null;
|
|
578
568
|
if (review && context.callId) input.onReview?.(context.callId, review);
|
|
579
569
|
const message =
|
|
580
570
|
review?.message ?? `${entry.appName}: ${entry.title}\nReview the validated arguments below before running this Action.`;
|
|
581
|
-
if (!(await context.requestApproval(message)))
|
|
571
|
+
if (!(await context.requestApproval(message))) {
|
|
572
|
+
if (input.actor.kind === "user") {
|
|
573
|
+
await recordRejectedAiCapability({ entry, actor: input.actor, args }).catch(() => undefined);
|
|
574
|
+
}
|
|
575
|
+
throw new Error("Capability Action was rejected by the user.");
|
|
576
|
+
}
|
|
582
577
|
}
|
|
583
578
|
return input.execute(entry, args, context);
|
|
584
579
|
}),
|
|
@@ -599,6 +594,11 @@ export const createAiResourceReaderTool = (input: {
|
|
|
599
594
|
outputSchema: z.unknown(),
|
|
600
595
|
approval: "never",
|
|
601
596
|
}).server(async (ref, context) => {
|
|
597
|
+
if (ref.type === "assistant.artifact") {
|
|
598
|
+
const tool = createCodeSourceTool("code_read");
|
|
599
|
+
if (tool.location !== "server") throw new Error("Code reader must execute on the server.");
|
|
600
|
+
return tool.run(CODE_SOURCE_TOOLS.code_read.input.parse({ id: ref.id }), context);
|
|
601
|
+
}
|
|
602
602
|
const appId = cloudResourceRefAppId(ref);
|
|
603
603
|
const app = input.apps.find((candidate) => candidate.appId === appId);
|
|
604
604
|
const reader = app ? resolveCapabilityResourceReader(app.manifest, ref) : null;
|
|
@@ -611,6 +611,24 @@ export const createAiResourceReaderTool = (input: {
|
|
|
611
611
|
});
|
|
612
612
|
|
|
613
613
|
/** Nessi resolver: one registry/load-state snapshot drives discovery, loading, schemas, and execution per model turn. */
|
|
614
|
+
/** Background discovery belongs to one run, never the interactive conversation. */
|
|
615
|
+
export const createRunToolStore = (initialNames: readonly string[]): Pick<AiConversationService, "getLoadedTools" | "loadTools"> => {
|
|
616
|
+
let current = [...new Set(initialNames)];
|
|
617
|
+
return {
|
|
618
|
+
getLoadedTools: async () => [...current],
|
|
619
|
+
loadTools: async ({ names, maxLoadedTools }) => {
|
|
620
|
+
const requested = [...new Set(names.map((name) => name.trim()).filter(Boolean))];
|
|
621
|
+
const alreadyLoaded = requested.filter((name) => current.includes(name));
|
|
622
|
+
const added = requested.filter((name) => !current.includes(name));
|
|
623
|
+
const combined = [...current, ...added];
|
|
624
|
+
const limit = Math.floor(maxLoadedTools ?? 0);
|
|
625
|
+
const evicted = limit > 0 ? combined.slice(0, Math.max(0, combined.length - limit)) : [];
|
|
626
|
+
current = combined.slice(evicted.length);
|
|
627
|
+
return { loaded: added.filter((name) => current.includes(name)), alreadyLoaded, evicted };
|
|
628
|
+
},
|
|
629
|
+
};
|
|
630
|
+
};
|
|
631
|
+
|
|
614
632
|
export const createAiToolResolver =
|
|
615
633
|
(input: {
|
|
616
634
|
conversationId: string;
|
|
@@ -621,12 +639,12 @@ export const createAiToolResolver =
|
|
|
621
639
|
store: Pick<AiConversationService, "getLoadedTools" | "loadTools">;
|
|
622
640
|
listRegistry?: () => Promise<CapabilityRegistryEntry[]>;
|
|
623
641
|
onCapabilityRegistryError?: (error: unknown) => void;
|
|
624
|
-
|
|
625
|
-
onHelpRegistryError?: (error: unknown) => void;
|
|
642
|
+
help?: HelpReaderFactory;
|
|
626
643
|
locale?: string;
|
|
627
644
|
maxLoadedTools?: number;
|
|
628
645
|
execute?: (entry: AiCapabilityCatalogEntry, args: unknown, context: ToolContext) => Promise<unknown>;
|
|
629
646
|
review?: (entry: AiCapabilityCatalogEntry, args: unknown, context: ToolContext) => Promise<CapabilityActionReview | null>;
|
|
647
|
+
authorizeBackground?: (entry: AiCapabilityCatalogEntry, args: unknown) => Promise<void>;
|
|
630
648
|
onReview?: (callId: string, review: CapabilityActionReview) => void;
|
|
631
649
|
onPrepared?: (snapshot: {
|
|
632
650
|
prepared: PreparedAiTools;
|
|
@@ -635,10 +653,9 @@ export const createAiToolResolver =
|
|
|
635
653
|
}) => void;
|
|
636
654
|
}): ToolResolver =>
|
|
637
655
|
async (): Promise<Tool[]> => {
|
|
638
|
-
const [registry, persistedLoadedNames
|
|
656
|
+
const [registry, persistedLoadedNames] = await Promise.all([
|
|
639
657
|
input.listRegistry ? resolveCapabilityRegistry(input.listRegistry, input.onCapabilityRegistryError) : [],
|
|
640
658
|
input.store.getLoadedTools({ conversationId: input.conversationId }),
|
|
641
|
-
input.listHelpRegistry ? resolveHelpRegistry(input.listHelpRegistry, input.onHelpRegistryError) : [],
|
|
642
659
|
]);
|
|
643
660
|
const configuredLimit = Math.floor(input.maxLoadedTools ?? 0);
|
|
644
661
|
const loadedNames = configuredLimit > 0 ? persistedLoadedNames.slice(-configuredLimit) : persistedLoadedNames;
|
|
@@ -651,9 +668,9 @@ export const createAiToolResolver =
|
|
|
651
668
|
}
|
|
652
669
|
const allowed = input.allowedTools == null ? null : new Set(input.allowedTools);
|
|
653
670
|
const capabilityCatalog = buildAiCapabilityCatalog(registry).filter((entry) => !allowed || allowed.has(entry.name));
|
|
654
|
-
const helpTools = input.
|
|
671
|
+
const helpTools = input.help ? createAiHelpTools(input.help, input.locale) : [];
|
|
655
672
|
const resourceTool =
|
|
656
|
-
input.execute && capabilityCatalog.length > 0
|
|
673
|
+
input.execute && (capabilityCatalog.length > 0 || input.staticTools.some((tool) => tool.def.name === "code_read"))
|
|
657
674
|
? createAiResourceReaderTool({ apps: registry, catalog: capabilityCatalog, execute: input.execute })
|
|
658
675
|
: null;
|
|
659
676
|
const builtIns = [...input.staticTools, ...helpTools, ...(resourceTool ? [resourceTool] : [])].filter(
|
|
@@ -679,8 +696,10 @@ export const createAiToolResolver =
|
|
|
679
696
|
? createLoadedAiCapabilityTools({
|
|
680
697
|
catalog: capabilityCatalog,
|
|
681
698
|
loadedNames,
|
|
699
|
+
actor: input.actor,
|
|
682
700
|
review: input.review,
|
|
683
701
|
onReview: input.onReview,
|
|
702
|
+
authorizeBackground: input.authorizeBackground,
|
|
684
703
|
execute: input.execute,
|
|
685
704
|
})
|
|
686
705
|
: []),
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { createHash } from "node:crypto";
|
|
2
2
|
import type { ToolContext } from "@k2b/nessi";
|
|
3
3
|
import { type CapabilityDispatchDependencies, dispatchCapability } from "../api/capabilities";
|
|
4
|
+
import { capabilityValueMeta, recordCapabilityExecution } from "../capabilities/executions";
|
|
4
5
|
import { type CapabilityActionManifest, type CapabilityActionReview, CapabilityActionReviewSchema } from "../contracts/capabilities";
|
|
5
6
|
import type { RequestActor } from "../server";
|
|
6
7
|
import type { AccessSubject } from "../server/services/access";
|
|
@@ -98,18 +99,24 @@ const dispatchAiCapability = async (input: AiCapabilityCall, review: boolean): P
|
|
|
98
99
|
const headers = new Headers();
|
|
99
100
|
if (input.locale) headers.set(LOCALE_HEADER, input.locale);
|
|
100
101
|
const action = input.entry.kind === "action" ? (input.entry.operation as CapabilityActionManifest) : null;
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
102
|
+
// One request id per dispatch links the tool call, the platform execution
|
|
103
|
+
// record, and the target app's own audit rows.
|
|
104
|
+
const requestId = crypto.randomUUID();
|
|
105
|
+
headers.set("x-request-id", requestId);
|
|
106
|
+
const key =
|
|
107
|
+
!review && action?.idempotency === "required" && input.context.callId
|
|
108
|
+
? idempotencyKey(input.conversationId, input.context.callId)
|
|
109
|
+
: null;
|
|
110
|
+
if (key) headers.set("idempotency-key", key);
|
|
111
|
+
if (!review && input.turnId && input.context.callId) {
|
|
112
|
+
await aiToolAudit.noteCapabilityDispatch({
|
|
113
|
+
conversationId: input.conversationId,
|
|
114
|
+
turnId: input.turnId,
|
|
115
|
+
callId: input.context.callId,
|
|
116
|
+
toolName: input.entry.name,
|
|
117
|
+
requestId,
|
|
118
|
+
idempotencyKey: key,
|
|
119
|
+
});
|
|
113
120
|
}
|
|
114
121
|
const request = new Request("http://cloud.internal/api/ai/capability", {
|
|
115
122
|
method: "POST",
|
|
@@ -120,6 +127,7 @@ const dispatchAiCapability = async (input: AiCapabilityCall, review: boolean): P
|
|
|
120
127
|
await dispatch({
|
|
121
128
|
request,
|
|
122
129
|
kind: input.entry.kind === "query" ? "queries" : "actions",
|
|
130
|
+
origin: "assistant",
|
|
123
131
|
review,
|
|
124
132
|
appId: input.entry.appId,
|
|
125
133
|
capabilityId: input.entry.operation.localId,
|
|
@@ -149,3 +157,30 @@ export const reviewAiCapability = async (input: AiCapabilityCall): Promise<Capab
|
|
|
149
157
|
};
|
|
150
158
|
|
|
151
159
|
export const executeAiCapability = (input: AiCapabilityCall): Promise<unknown> => dispatchAiCapability(input, false);
|
|
160
|
+
|
|
161
|
+
/**
|
|
162
|
+
* A rejected approval never reaches the dispatcher, so the assistant records
|
|
163
|
+
* the same execution row the dispatcher would have written for the attempt.
|
|
164
|
+
*/
|
|
165
|
+
export const recordRejectedAiCapability = async (input: {
|
|
166
|
+
entry: Pick<AiCapabilityCatalogEntry, "appId" | "kind" | "name" | "operation">;
|
|
167
|
+
actor: CapabilityActor;
|
|
168
|
+
args: unknown;
|
|
169
|
+
}): Promise<void> => {
|
|
170
|
+
const at = new Date();
|
|
171
|
+
await recordCapabilityExecution({
|
|
172
|
+
requestId: crypto.randomUUID(),
|
|
173
|
+
origin: "assistant",
|
|
174
|
+
appId: input.entry.appId,
|
|
175
|
+
capability: input.entry.name,
|
|
176
|
+
kind: input.entry.kind,
|
|
177
|
+
destructive: "destructive" in input.entry.operation ? input.entry.operation.destructive : false,
|
|
178
|
+
actorKind: "user",
|
|
179
|
+
actorId: input.actor.user.id,
|
|
180
|
+
userId: input.actor.user.id,
|
|
181
|
+
status: "rejected",
|
|
182
|
+
inputMeta: capabilityValueMeta(input.args),
|
|
183
|
+
startedAt: at,
|
|
184
|
+
completedAt: at,
|
|
185
|
+
});
|
|
186
|
+
};
|
package/src/ai/chat/blocks.tsx
CHANGED
|
@@ -1,9 +1,11 @@
|
|
|
1
|
+
import { hasCapabilityTable } from "./capability-result";
|
|
1
2
|
import { dates } from "@k2b/stdlib";
|
|
2
3
|
import { mutation } from "@k2b/stdlib/solid";
|
|
3
|
-
import { Button, ButtonLink, Chat, isStructuredDataValue, SplitButton, StructuredDataPreview, useLocale } from "@k2b/ui";
|
|
4
|
-
import { createSignal, For, type JSX, Match, Show, Switch } from "solid-js";
|
|
4
|
+
import { Button, ButtonLink, Chat, MarkdownView, isStructuredDataValue, SplitButton, StructuredDataPreview, useLocale } from "@k2b/ui";
|
|
5
|
+
import { createMemo, createSignal, For, type JSX, Match, Show, Switch } from "solid-js";
|
|
5
6
|
import type { CapabilityActionReview } from "../../contracts/capabilities";
|
|
6
7
|
import { markdown } from "../../shared";
|
|
8
|
+
import { groupToolBlocks, isFailedTool, summarizeToolGroup } from "./tool-groups";
|
|
7
9
|
import type { AiTurnBlock } from "../protocol";
|
|
8
10
|
import { isRenderableTurnBlock } from "../protocol";
|
|
9
11
|
import { hasSpecializedBuiltinToolView, SpecializedBuiltinToolBlock } from "./builtin-tools";
|
|
@@ -288,14 +290,23 @@ function ApprovalBlockView(props: { turnId: string; block: ToolBlock }) {
|
|
|
288
290
|
{(detail) => (
|
|
289
291
|
<section class="min-w-0" aria-label={detail.label}>
|
|
290
292
|
<h4 class="mb-1.5 font-semibold text-primary">{detail.label}</h4>
|
|
291
|
-
<
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
293
|
+
<Show
|
|
294
|
+
when={detail.format === "markdown"}
|
|
295
|
+
fallback={
|
|
296
|
+
<pre
|
|
297
|
+
class="max-h-72 overflow-auto whitespace-pre-wrap break-words pr-2 font-sans text-xs leading-5 text-secondary"
|
|
298
|
+
role="region"
|
|
299
|
+
tabIndex={0}
|
|
300
|
+
aria-label={`${detail.label} content`}
|
|
301
|
+
>
|
|
302
|
+
<ReviewDetailValue detail={detail} />
|
|
303
|
+
</pre>
|
|
304
|
+
}
|
|
296
305
|
>
|
|
297
|
-
<
|
|
298
|
-
|
|
306
|
+
<div class="max-h-72 overflow-auto text-xs text-secondary" role="region" tabIndex={0} aria-label={detail.label}>
|
|
307
|
+
<MarkdownView class="cloud-ai-approval-detail" markdown={detail.value} headingScale="compact" allowImages={false} />
|
|
308
|
+
</div>
|
|
309
|
+
</Show>
|
|
299
310
|
</section>
|
|
300
311
|
)}
|
|
301
312
|
</For>
|
|
@@ -496,9 +507,6 @@ function CapabilityToolView(props: { block: ToolBlock }) {
|
|
|
496
507
|
</Show>
|
|
497
508
|
</Show>
|
|
498
509
|
</Show>
|
|
499
|
-
<Show when={props.block.status !== "running" && !props.block.isError}>
|
|
500
|
-
<CapabilityTablePreview result={props.block.result} label={label()} />
|
|
501
|
-
</Show>
|
|
502
510
|
</>
|
|
503
511
|
);
|
|
504
512
|
}
|
|
@@ -618,85 +626,138 @@ function MemoryToolView(props: { block: ToolBlock }) {
|
|
|
618
626
|
}
|
|
619
627
|
|
|
620
628
|
function ToolBlockView(props: { turnId: string; block: ToolBlock; active?: boolean }) {
|
|
629
|
+
const actions = useAiChatActions();
|
|
621
630
|
const status = () => props.block.status;
|
|
622
631
|
const fetchError = () =>
|
|
623
632
|
props.block.name === "fetch_file" && props.block.isError ? fetchFileErrorPresentation(props.block.result) : undefined;
|
|
624
633
|
return (
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
<
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
<
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
<
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
<
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
<
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
<
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
<
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
<
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
<
|
|
634
|
+
<>
|
|
635
|
+
<Switch
|
|
636
|
+
fallback={
|
|
637
|
+
<ToolResultDisclosure
|
|
638
|
+
blockId={props.block.id}
|
|
639
|
+
name={displayToolName(props.block.name)}
|
|
640
|
+
toolName={props.block.name}
|
|
641
|
+
args={props.block.args}
|
|
642
|
+
result={props.block.result}
|
|
643
|
+
isError={Boolean(props.block.isError)}
|
|
644
|
+
errorLabel={fetchError()?.label}
|
|
645
|
+
errorDescription={fetchError()?.description}
|
|
646
|
+
/>
|
|
647
|
+
}
|
|
648
|
+
>
|
|
649
|
+
<Match when={status() === "awaiting_approval"}>
|
|
650
|
+
<ApprovalBlockView turnId={props.turnId} block={props.block} />
|
|
651
|
+
</Match>
|
|
652
|
+
<Match when={status() === "rejected"}>
|
|
653
|
+
<RejectedToolView block={props.block} />
|
|
654
|
+
</Match>
|
|
655
|
+
<Match when={props.block.presentation?.kind === "capability"}>
|
|
656
|
+
<CapabilityToolView block={props.block} />
|
|
657
|
+
</Match>
|
|
658
|
+
<Match when={actions.renderCodePresentation && props.block.name === "code_present" && status() === "completed" && !props.block.isError}>
|
|
659
|
+
{actions.renderCodePresentation?.(props.block.result)}
|
|
660
|
+
</Match>
|
|
661
|
+
<Match when={props.block.name === "present" && !props.block.isError}>
|
|
662
|
+
<PresentToolBlock block={props.block} />
|
|
663
|
+
</Match>
|
|
664
|
+
<Match when={props.block.name === "web_search" && !props.block.isError}>
|
|
665
|
+
<WebSearchToolBlock block={props.block} />
|
|
666
|
+
</Match>
|
|
667
|
+
<Match when={props.block.name === "web_extract" && !props.block.isError}>
|
|
668
|
+
<WebExtractToolBlock block={props.block} />
|
|
669
|
+
</Match>
|
|
670
|
+
<Match when={props.block.name === "fetch_file" && !props.block.isError}>
|
|
671
|
+
<FetchFileToolBlock block={props.block} />
|
|
672
|
+
</Match>
|
|
673
|
+
<Match when={props.block.name === "memory"}>
|
|
674
|
+
<MemoryToolView block={props.block} />
|
|
675
|
+
</Match>
|
|
676
|
+
<Match when={hasSpecializedBuiltinToolView(props.block.name, props.block.result) && status() === "completed" && !props.block.isError}>
|
|
677
|
+
<SpecializedBuiltinToolBlock block={props.block} />
|
|
678
|
+
</Match>
|
|
679
|
+
<Match when={isCardToolName(props.block.name) && !props.block.isError}>
|
|
680
|
+
<CloudCardBlock args={props.block.args} />
|
|
681
|
+
</Match>
|
|
682
|
+
<Match when={isSurveyToolName(props.block.name) && !props.block.isError}>
|
|
683
|
+
<SurveyToolView turnId={props.turnId} block={props.block} active={props.active} />
|
|
684
|
+
</Match>
|
|
685
|
+
<Match when={isTextEditorToolName(props.block.name)}>
|
|
686
|
+
<TextEditorToolView turnId={props.turnId} block={props.block} active={props.active} />
|
|
687
|
+
</Match>
|
|
688
|
+
<Match when={status() === "running" || status() === "awaiting_client"}>
|
|
689
|
+
<Chat.Activity label={displayToolName(props.block.name)} icon={aiToolIcon(props.block.name)} busy />
|
|
690
|
+
</Match>
|
|
691
|
+
</Switch>
|
|
692
|
+
<Show when={hasCapabilityTable(props.block)}>
|
|
693
|
+
<CapabilityTablePreview result={props.block.result} label={props.block.presentation?.title ?? displayToolName(props.block.name)} />
|
|
694
|
+
</Show>
|
|
695
|
+
</>
|
|
696
|
+
);
|
|
697
|
+
}
|
|
698
|
+
|
|
699
|
+
/** Render one unified turn block. Shared by persisted assistant groups and the live turn. */
|
|
700
|
+
export function AiTurnBlockView(props: { block: AiTurnBlock; turnId: string; streaming?: boolean; active?: boolean }) {
|
|
701
|
+
// The id/kind remain stable while the immutable block value changes during a turn.
|
|
702
|
+
return (
|
|
703
|
+
<Switch>
|
|
704
|
+
<Match when={props.block.kind === "text" && props.block}>
|
|
705
|
+
{block => <AssistantMarkdownBlock html={markdown.renderSync(block().text)} />}
|
|
665
706
|
</Match>
|
|
666
|
-
<Match when={
|
|
667
|
-
<
|
|
707
|
+
<Match when={props.block.kind === "thinking" && props.block}>
|
|
708
|
+
{block => <ThinkingBlockView text={block().text} streaming={props.streaming} />}
|
|
668
709
|
</Match>
|
|
669
|
-
<Match when={
|
|
670
|
-
<
|
|
710
|
+
<Match when={props.block.kind === "steer_applied"}>
|
|
711
|
+
<Chat.Activity label="Conversation steered" icon="ti ti-route" tone="ai" />
|
|
671
712
|
</Match>
|
|
672
|
-
<Match when={
|
|
673
|
-
<
|
|
713
|
+
<Match when={props.block.kind === "tool" && props.block}>
|
|
714
|
+
{block => <ToolBlockView turnId={props.turnId} block={block()} active={props.active} />}
|
|
674
715
|
</Match>
|
|
675
|
-
<Match when={
|
|
676
|
-
<
|
|
716
|
+
<Match when={props.block.kind === "compaction" && props.block}>
|
|
717
|
+
{block => <CompactionBlockView block={block()} />}
|
|
677
718
|
</Match>
|
|
678
719
|
</Switch>
|
|
679
720
|
);
|
|
680
721
|
}
|
|
681
722
|
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
const
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
723
|
+
function CompactToolRow(props: { block: ToolBlock; busy?: boolean }) {
|
|
724
|
+
const locale = useLocale();
|
|
725
|
+
const de = () => locale().startsWith("de");
|
|
726
|
+
const args = () => isRecord(props.block.args) ? props.block.args : {};
|
|
727
|
+
const detail = () => [args().path, args().name, args().query].find((value) => typeof value === "string");
|
|
728
|
+
const waiting = () => props.block.status === "awaiting_client";
|
|
729
|
+
return <AiToolActivity blockId={props.block.id} label={props.block.presentation?.title ?? displayToolName(props.block.name)}
|
|
730
|
+
description={[props.block.status === "running" ? props.block.progress ?? "" : "", typeof detail() === "string" ? String(detail()) : "", waiting() ? (de() ? "Wartet" : "Waiting") : isFailedTool(props.block) ? (de() ? "Fehlgeschlagen" : "Failed") : ""].filter(Boolean).join(" · ")}
|
|
731
|
+
icon={waiting() ? "ti ti-clock" : aiToolIcon(props.block.name, props.block.presentation?.appIcon)}
|
|
732
|
+
tone={isFailedTool(props.block) ? "danger" : "neutral"} busy={props.busy && !waiting()} bodyInset={false} renderBody={() =>
|
|
733
|
+
<div class="max-h-72 overflow-auto overscroll-contain rounded-md bg-zinc-100 p-3 dark:bg-zinc-950" tabIndex={0} role="region" aria-label={de() ? "Werkzeug-Eingabe und Ausgabe" : "Tool input and output"}>
|
|
734
|
+
<ToolDetail title={de() ? "Eingabe" : "Input"} toolName={props.block.name} value={props.block.args} />
|
|
735
|
+
<ToolDetail title={de() ? "Ausgabe" : "Output"} toolName={props.block.name} value={props.block.result} />
|
|
736
|
+
</div>}
|
|
737
|
+
/>;
|
|
738
|
+
}
|
|
739
|
+
|
|
740
|
+
function ToolGroupView(props: { blocks: ToolBlock[]; active: boolean }) {
|
|
741
|
+
const locale = useLocale();
|
|
742
|
+
const last = () => props.blocks.at(-1)!;
|
|
743
|
+
const waiting = () => last().status === "awaiting_client";
|
|
744
|
+
const multiple = () => props.blocks.length > 1;
|
|
745
|
+
const detail = () => {
|
|
746
|
+
const args = last().args;
|
|
747
|
+
return isRecord(args) ? [args.path,args.name,args.query].find(value=>typeof value==="string") : undefined;
|
|
748
|
+
};
|
|
749
|
+
return <AiToolActivity blockId={`group:${props.blocks[0]!.id}`} bodyInset={false}
|
|
750
|
+
label={props.active || !multiple() ? (last().presentation?.title ?? displayToolName(last().name)) : summarizeToolGroup(props.blocks, locale())}
|
|
751
|
+
description={waiting() ? (locale().startsWith("de") ? "Wartet" : "Waiting") : last().status === "running" ? last().progress : !multiple() && typeof detail()==="string" ? String(detail()) : undefined}
|
|
752
|
+
icon={props.active || !multiple() ? (waiting() ? "ti ti-clock" : aiToolIcon(last().name)) : "ti ti-stack"}
|
|
753
|
+
tone={!multiple() && isFailedTool(last()) ? "danger" : "neutral"}
|
|
754
|
+
busy={props.active && !waiting()} renderBody={() => <Show when={multiple()} fallback={
|
|
755
|
+
<div class="max-h-72 overflow-auto overscroll-contain rounded-md bg-zinc-100 p-3 dark:bg-zinc-950" tabIndex={0} role="region" aria-label={locale().startsWith("de") ? "Werkzeug-Eingabe und Ausgabe" : "Tool input and output"}>
|
|
756
|
+
<ToolDetail title={locale().startsWith("de") ? "Eingabe" : "Input"} toolName={last().name} value={last().args} />
|
|
757
|
+
<ToolDetail title={locale().startsWith("de") ? "Ausgabe" : "Output"} toolName={last().name} value={last().result} />
|
|
758
|
+
</div>
|
|
759
|
+
}><For each={props.blocks}>{(block) => <CompactToolRow block={block} />}</For></Show>}
|
|
760
|
+
/>;
|
|
700
761
|
}
|
|
701
762
|
|
|
702
763
|
export function AiTurnBlockList(props: {
|
|
@@ -707,24 +768,25 @@ export function AiTurnBlockList(props: {
|
|
|
707
768
|
active?: boolean;
|
|
708
769
|
disclosureState?: AiToolDisclosureState;
|
|
709
770
|
}) {
|
|
710
|
-
const
|
|
771
|
+
const groups = createMemo(() => groupToolBlocks(props.blocks.filter(isRenderableTurnBlock)));
|
|
772
|
+
const keyedGroups = createMemo(() => new Map(groups().map(group => [group.kind === "tools" ? `tools:${group.blocks[0]!.id}` : `block:${group.block.id}`, group])));
|
|
773
|
+
const keys = createMemo(() => [...keyedGroups().keys()]);
|
|
711
774
|
const disclosureState = props.disclosureState ?? createAiToolDisclosureState();
|
|
712
775
|
return (
|
|
713
|
-
<Show when={
|
|
714
|
-
<
|
|
715
|
-
<
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
</Show>
|
|
776
|
+
<Show when={groups().length > 0}><AiToolDisclosureProvider state={disclosureState}>
|
|
777
|
+
<div class={`flex flex-col ${props.compact ? "gap-1" : "gap-2"}`}>
|
|
778
|
+
<For each={keys()}>{(key, index) => {
|
|
779
|
+
const group = () => keyedGroups().get(key)!;
|
|
780
|
+
return <Switch>
|
|
781
|
+
<Match when={(() => { const value = group(); return value.kind === "tools" ? value : undefined; })()}>
|
|
782
|
+
{(tools) => <ToolGroupView blocks={tools().blocks} active={Boolean(props.active && index() === keys().length - 1)} />}
|
|
783
|
+
</Match>
|
|
784
|
+
<Match when={(() => { const value = group(); return value.kind === "block" ? value : undefined; })()}>
|
|
785
|
+
{(item) => <AiTurnBlockView block={item().block} turnId={props.turnId} streaming={props.streaming && index() === keys().length - 1} active={props.active} />}
|
|
786
|
+
</Match>
|
|
787
|
+
</Switch>;
|
|
788
|
+
}}</For>
|
|
789
|
+
</div>
|
|
790
|
+
</AiToolDisclosureProvider></Show>
|
|
729
791
|
);
|
|
730
792
|
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { CapabilityTablePresentationSchema, capabilityDataAtPath } from "../../contracts/capabilities";
|
|
2
|
+
import type { AiTurnBlock } from "../protocol";
|
|
3
|
+
|
|
4
|
+
export const capabilityTable = (result: unknown) => {
|
|
5
|
+
if (!result || typeof result !== "object") return null;
|
|
6
|
+
const presentation = CapabilityTablePresentationSchema.safeParse(capabilityDataAtPath(result, ["presentation"]));
|
|
7
|
+
if (!presentation.success) return null;
|
|
8
|
+
const rows = capabilityDataAtPath(capabilityDataAtPath(result, ["data"]), presentation.data.rowsPath);
|
|
9
|
+
if (!Array.isArray(rows)) return null;
|
|
10
|
+
return { presentation: presentation.data, rows };
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
/** Result presentation is independent of optional app branding. */
|
|
14
|
+
export const hasCapabilityTable = (block: Extract<AiTurnBlock, { kind: "tool" }>): boolean =>
|
|
15
|
+
block.status === "completed" && !block.isError && capabilityTable(block.result) !== null;
|
|
@@ -1,17 +1,9 @@
|
|
|
1
|
+
import { capabilityTable } from "./capability-result";
|
|
1
2
|
import { ButtonLink, DataTable, type DataTableColumn, useLocale } from "@k2b/ui";
|
|
2
3
|
import { createMemo, For, Show } from "solid-js";
|
|
3
|
-
import { CapabilitySemanticLinkSchema,
|
|
4
|
+
import { CapabilitySemanticLinkSchema, capabilityDataAtPath } from "../../contracts/capabilities";
|
|
4
5
|
import { aiChatMessages } from "./messages";
|
|
5
6
|
|
|
6
|
-
export const capabilityTable = (result: unknown) => {
|
|
7
|
-
if (!result || typeof result !== "object") return null;
|
|
8
|
-
const presentation = CapabilityTablePresentationSchema.safeParse(capabilityDataAtPath(result, ["presentation"]));
|
|
9
|
-
if (!presentation.success) return null;
|
|
10
|
-
const rows = capabilityDataAtPath(capabilityDataAtPath(result, ["data"]), presentation.data.rowsPath);
|
|
11
|
-
if (!Array.isArray(rows)) return null;
|
|
12
|
-
return { presentation: presentation.data, rows };
|
|
13
|
-
};
|
|
14
|
-
|
|
15
7
|
export const capabilityCellText = (value: unknown): string => {
|
|
16
8
|
if (value === undefined || value === null) return "—";
|
|
17
9
|
if (typeof value === "string") return value;
|