@k2b/cloud 0.40.0 → 0.42.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/package.json +9 -5
- package/src/_internal/bounded-json.ts +14 -2
- package/src/_internal/capabilities.ts +87 -8
- package/src/_internal/capability-reviews.ts +6 -0
- package/src/_internal/capability-sentences.ts +2 -1
- package/src/_internal/capability-timeout.ts +71 -0
- package/src/_internal/define-app.ts +158 -92
- package/src/_internal/heartbeat.ts +203 -14
- package/src/_internal/http-drain.ts +56 -0
- package/src/_internal/registry-validation.ts +3 -0
- package/src/_internal/registry.ts +28 -1
- package/src/_internal/runtime-context.ts +1 -0
- package/src/_internal/setup-lock.ts +103 -0
- package/src/_internal/status-preserving-ssr.ts +2 -0
- package/src/access/PermissionEditor.tsx +16 -17
- package/src/access/PrincipalPicker.tsx +45 -10
- package/src/access/messages.ts +8 -0
- package/src/access/principal-label.ts +18 -0
- package/src/access/ui.ts +1 -0
- package/src/account/EntitySearch.tsx +5 -4
- package/src/ai/action-review.ts +2 -1
- package/src/ai/app-skill-store.ts +1 -1
- package/src/ai/capabilities.ts +21 -3
- package/src/ai/capability-execution.ts +14 -12
- package/src/ai/chat/action-card.ts +76 -4
- package/src/ai/chat/action-messages.ts +14 -0
- package/src/ai/chat/action-question.browser-harness.tsx +12 -0
- package/src/ai/chat/action-question.tsx +214 -23
- package/src/ai/chat/action-text.ts +288 -0
- package/src/ai/chat/tool-action-words.ts +31 -0
- package/src/ai/chat/tool-action.tsx +1 -26
- package/src/ai/chat-tasks.ts +2 -1
- package/src/ai/client/controller.ts +150 -32
- package/src/ai/client/transport.ts +14 -2
- package/src/ai/code-capability-routes.ts +27 -23
- package/src/ai/code-runtime-tools.ts +14 -16
- package/src/ai/code-source-tools.ts +16 -18
- package/src/ai/executor.ts +64 -21
- package/src/ai/fetch-file-tool.ts +67 -76
- package/src/ai/firecrawl-tools.ts +15 -2
- package/src/ai/http.ts +1 -0
- package/src/ai/index.ts +1 -0
- package/src/ai/migrate.ts +28 -0
- package/src/ai/open-questions.ts +168 -0
- package/src/ai/projects.ts +1 -2
- package/src/ai/protocol.ts +2 -2
- package/src/ai/question-text.ts +8 -0
- package/src/ai/recent-resources.ts +12 -5
- package/src/ai/review-content.ts +77 -0
- package/src/ai/routes.ts +119 -35
- package/src/ai/runtime.ts +1 -0
- package/src/ai/skill-tool.ts +17 -6
- package/src/ai/skills-routes.ts +72 -27
- package/src/ai/skills.ts +139 -60
- package/src/ai/store.ts +184 -31
- package/src/ai/testing.ts +36 -0
- package/src/ai/tool-sentences.ts +152 -34
- package/src/ai/tool-wording.ts +12 -6
- package/src/ai/tools.ts +8 -3
- package/src/ai/types.ts +25 -4
- package/src/ai/usage.ts +2 -2
- package/src/api/admin-core-settings.ts +15 -2
- package/src/api/admin-geo.ts +54 -0
- package/src/api/admin-linux-identities.ts +6 -3
- package/src/api/admin-outgoing-mail.ts +29 -15
- package/src/api/app-approval-actions.ts +31 -7
- package/src/api/approvals-loader.ts +3 -3
- package/src/api/approvals.ts +51 -0
- package/src/api/apps.ts +4 -2
- package/src/api/auth/schemas.ts +3 -0
- package/src/api/auth.ts +54 -18
- package/src/api/capabilities.ts +258 -181
- package/src/api/capability-streams.ts +21 -13
- package/src/api/cli-plugins.ts +4 -1
- package/src/api/geo-glyphs.ts +11 -0
- package/src/api/geo.ts +128 -0
- package/src/api/help.ts +12 -4
- package/src/api/index.ts +5 -0
- package/src/api/mcp.ts +73 -41
- package/src/api/me-panel-consent.ts +8 -5
- package/src/api/me.ts +79 -3
- package/src/api/search.ts +21 -14
- package/src/api/sync-ops.ts +19 -13
- package/src/api/widgets.ts +42 -18
- package/src/approvals/Approvals.tsx +30 -66
- package/src/approvals/approval-card.ts +68 -0
- package/src/browser/CloudResourceSearch.browser-harness.tsx +53 -1
- package/src/browser/CloudResourceSearch.tsx +3 -13
- package/src/browser/PanelLeaveNotice.tsx +18 -0
- package/src/browser/navigation-search.ts +78 -25
- package/src/browser/panel-leave.ts +69 -0
- package/src/browser/panel-messages.ts +12 -0
- package/src/browser/panel-slot.ts +173 -24
- package/src/browser/panel.ts +3 -0
- package/src/browser/resource-search-messages.ts +2 -0
- package/src/capabilities/client.ts +28 -1
- package/src/capabilities/executions.ts +22 -3
- package/src/capabilities/geo-client.ts +57 -0
- package/src/capabilities/response.ts +2 -1
- package/src/capabilities/review-storage.ts +24 -0
- package/src/capabilities/reviews.ts +17 -3
- package/src/capabilities/server.ts +36 -16
- package/src/cli/access.ts +15 -23
- package/src/cli/admin/linux.ts +13 -2
- package/src/cli/admin/outgoing-mail.ts +26 -4
- package/src/cli/capabilities.ts +19 -0
- package/src/config/env.ts +0 -6
- package/src/contracts/app-access.ts +48 -0
- package/src/contracts/app-approval.ts +3 -3
- package/src/contracts/app.ts +4 -1
- package/src/contracts/approvals.ts +2 -2
- package/src/contracts/avatar.ts +186 -0
- package/src/contracts/capabilities.ts +149 -55
- package/src/contracts/geo-admin.ts +127 -0
- package/src/contracts/geo-map.ts +19 -0
- package/src/contracts/geo-route.ts +168 -0
- package/src/contracts/geo.ts +248 -0
- package/src/contracts/index.ts +4 -0
- package/src/contracts/outgoing-mail.ts +17 -4
- package/src/contracts/registry.ts +2 -0
- package/src/contracts/shared.ts +3 -0
- package/src/events/app-access.ts +68 -0
- package/src/events/live-delivery.ts +128 -0
- package/src/events/live.ts +14 -130
- package/src/geo/CloudMap.tsx +95 -0
- package/src/geo/map-messages.ts +17 -0
- package/src/geo/ui.ts +1 -0
- package/src/index.ts +2 -0
- package/src/server/client-ip.ts +36 -0
- package/src/server/index.ts +3 -4
- package/src/server/middleware/app-access.ts +312 -0
- package/src/server/middleware/auth.ts +5 -3
- package/src/server/middleware/invocation.ts +11 -0
- package/src/server/middleware/rate-limit.ts +4 -4
- package/src/server/services/access.ts +13 -9
- package/src/server/services/index.ts +0 -2
- package/src/server/services/services.ts +0 -2
- package/src/services/accounts/app.ts +23 -3
- package/src/services/accounts/posix.ts +21 -7
- package/src/services/accounts/users.ts +12 -1
- package/src/services/allowed-network-fetch.ts +16 -10
- package/src/services/app-access-metrics.ts +24 -0
- package/src/services/app-access.ts +123 -0
- package/src/services/app-discovery.ts +27 -0
- package/src/services/approvals/content.ts +40 -0
- package/src/services/approvals/device.ts +49 -33
- package/src/services/approvals/index.ts +15 -6
- package/src/services/approvals/list.ts +6 -11
- package/src/services/approvals/question.ts +72 -0
- package/src/services/audit/index.ts +1 -0
- package/src/services/auth-flows/ipa.ts +39 -2
- package/src/services/auth-flows/password-reset.ts +32 -6
- package/src/services/downstream-budget.ts +87 -0
- package/src/services/geo/admin.ts +262 -0
- package/src/services/geo/bucket.ts +67 -0
- package/src/services/geo/cache.ts +57 -0
- package/src/services/geo/costs.ts +5 -0
- package/src/services/geo/errors.ts +41 -0
- package/src/services/geo/fixtures/README.md +17 -0
- package/src/services/geo/fixtures/along.json +355 -0
- package/src/services/geo/fixtures/autocomplete.json +218 -0
- package/src/services/geo/fixtures/geocode.json +129 -0
- package/src/services/geo/fixtures/places.json +355 -0
- package/src/services/geo/fixtures/reverse.json +61 -0
- package/src/services/geo/fixtures/routing.json +57 -0
- package/src/services/geo/geoapify-route.ts +93 -0
- package/src/services/geo/geoapify.ts +281 -0
- package/src/services/geo/ledger.ts +122 -0
- package/src/services/geo/map.ts +160 -0
- package/src/services/geo/migrate.ts +25 -0
- package/src/services/geo/polyline.ts +184 -0
- package/src/services/geo/route-chain.ts +145 -0
- package/src/services/geo/route-costs.ts +9 -0
- package/src/services/geo/route-geometry.ts +39 -0
- package/src/services/geo/route-plan.ts +172 -0
- package/src/services/geo/route-service.ts +433 -0
- package/src/services/geo/service.ts +300 -0
- package/src/services/geo/settings.ts +49 -0
- package/src/services/help/index.ts +16 -7
- package/src/services/help/types.ts +6 -1
- package/src/services/identity/invocation-actor.ts +20 -2
- package/src/services/identity/issue-invocation.ts +67 -0
- package/src/services/identity/key-ring.ts +5 -3
- package/src/services/identity/runtime-config.ts +23 -0
- package/src/services/index.ts +11 -0
- package/src/services/mandates/index.ts +40 -3
- package/src/services/network-security.ts +6 -3
- package/src/services/network-transport.ts +99 -0
- package/src/services/notifications/catalog.ts +3 -2
- package/src/services/notifications/runtime.ts +1 -0
- package/src/services/notifications/web-push-transport.ts +45 -38
- package/src/services/outgoing-mail/dispatcher.ts +6 -1
- package/src/services/outgoing-mail/index.ts +55 -1
- package/src/services/outgoing-mail/messages.ts +82 -3
- package/src/services/outgoing-mail/resolve-host.ts +2 -2
- package/src/services/outgoing-mail/retention.ts +13 -1
- package/src/services/outgoing-mail/store.ts +36 -6
- package/src/services/outgoing-mail/test-send.ts +34 -4
- package/src/services/outgoing-mail/transport.ts +1 -1
- package/src/services/pdf/gotenberg.ts +11 -38
- package/src/services/pdf/markdown.ts +160 -2
- package/src/services/pdf/template-preview.ts +1 -1
- package/src/services/public-http.ts +28 -40
- package/src/services/session/index.ts +203 -29
- package/src/services/settings/app.ts +4 -1
- package/src/services/settings/core-settings.ts +88 -0
- package/src/services/settings/defaults.ts +8 -2
- package/src/services/settings/errors.ts +11 -0
- package/src/services/settings/geo-budget.ts +40 -0
- package/src/services/settings/index.ts +4 -3
- package/src/services/settings/store.ts +25 -17
- package/src/services/weather/geo.ts +29 -111
- package/src/services/weather/index.ts +4 -3
- package/src/services/weather/locations.ts +8 -5
- package/src/services/weather/migrate.ts +1 -0
- package/src/services/weather/settings.ts +0 -16
- package/src/shared/__snapshots__/avatar-art.test.ts.snap +5 -0
- package/src/shared/avatar-art.ts +400 -0
- package/src/shared/index.ts +2 -0
- package/src/shared/network-address.ts +22 -3
- package/src/shared/template-rendering.ts +36 -4
- package/src/shared-text/documents.ts +1 -1
- package/src/shared-text/index.ts +2 -0
- package/src/shared-text/log.ts +51 -3
- package/src/shared-text/snapshots.ts +340 -0
- package/src/ssr/AdminLayout.tsx +4 -2
- package/src/ssr/CloudShell.island.tsx +6 -0
- package/src/ssr/HelpPanelArticle.island.tsx +308 -0
- package/src/ssr/HelpPanelBack.island.tsx +32 -0
- package/src/ssr/HelpPanelMenu.island.tsx +133 -0
- package/src/ssr/HelpPanelSearch.island.tsx +123 -0
- package/src/ssr/HelpPanelShortcuts.island.tsx +22 -0
- package/src/ssr/HelpPanelView.island.tsx +27 -0
- package/src/ssr/Layout.tsx +65 -15
- package/src/ssr/LayoutHelp.browser-harness.tsx +1 -1
- package/src/ssr/LayoutHelp.tsx +185 -542
- package/src/ssr/LayoutHelpPanel.browser-harness.tsx +6 -7
- package/src/ssr/LayoutHelpPanel.server.browser-harness.tsx +131 -0
- package/src/ssr/LayoutHelpPanel.server.tsx +115 -0
- package/src/ssr/PanelRuntime.island.tsx +11 -7
- package/src/ssr/PwaLayout.tsx +2 -2
- package/src/ssr/RailApps.tsx +6 -0
- package/src/ssr/ShellRuntime.island.tsx +23 -7
- package/src/ssr/__snapshots__/Layout.unchanged.render.test.tsx.snap +151 -0
- package/src/ssr/__snapshots__/LayoutHelp.ssr.render.test.tsx.snap +29 -0
- package/src/ssr/admin-navigation.ts +1 -0
- package/src/ssr/app-badges.ts +6 -1
- package/src/ssr/compact-shell.browser-harness.ts +44 -5
- package/src/ssr/help.ts +7 -5
- package/src/ssr/index.ts +2 -1
- package/src/ssr/islands/index.ts +3 -4
- package/src/ssr/layout-context.ts +2 -0
- package/src/ssr/layout-help-panel-controls.tsx +43 -0
- package/src/ssr/layout-help-panel-document.ts +67 -0
- package/src/ssr/layout-help-panel-lifecycle.ts +32 -0
- package/src/ssr/layout-help-panel-menu.tsx +113 -0
- package/src/ssr/layout-help-panel-modules.ts +7 -0
- package/src/ssr/layout-help-panel-reader.tsx +159 -0
- package/src/ssr/layout-help-panel-search.tsx +69 -0
- package/src/ssr/layout-help-panel-shortcuts.ts +7 -0
- package/src/ssr/layout-help-panel-state.ts +141 -0
- package/src/ssr/layout-help-panel-types.ts +19 -0
- package/src/ssr/layout-help-views.tsx +484 -0
- package/src/ssr/mobile-menu-history.ts +4 -0
- package/src/ssr/page-swap-fixture/Column.island.tsx +15 -0
- package/src/ssr/page-swap-fixture/ProviderProbe.island.tsx +31 -0
- package/src/ssr/page-swap.browser-harness.tsx +177 -0
- package/src/ssr/page-swap.ts +28 -0
- package/src/ssr/page-trail-runtime.ts +32 -13
- package/src/ssr/panel-consent.ts +2 -2
- package/src/ssr/panel-idle.ts +9 -0
- package/src/ssr/panel-slot.browser-harness.tsx +3 -0
- package/src/ssr/platform-messages.ts +8 -0
- package/src/ssr/runtime.ts +17 -0
- package/src/ssr/shell-data.tsx +10 -2
- package/src/styles/approvals.css +6 -0
- package/src/styles/effects.css +15 -0
- package/src/styles/global.css +70 -6
- package/src/server/services/geo.ts +0 -215
package/src/ai/store.ts
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import type { DoneReason, InboundEvent, LoopAggregate, Message, SessionStore, StoreEntry } from "@k2b/nessi";
|
|
2
2
|
import type { Usage } from "@k2b/nessi/ai";
|
|
3
3
|
import { type SQL, sql } from "bun";
|
|
4
|
-
import {
|
|
4
|
+
import { capabilityRequestHash } from "../capabilities/claims";
|
|
5
|
+
import { restoreReviewSnapshot, serializeReviewSnapshot } from "../capabilities/review-storage";
|
|
6
|
+
import { type CapabilityActionReviewPresentation, CapabilityActionReviewPresentationSchema } from "../contracts/capabilities";
|
|
5
7
|
import { logger } from "../services/logging";
|
|
6
8
|
import { toPgTextArray } from "../services/postgres";
|
|
7
9
|
import { AI_TOOL_CHECK_STALE_MS, type AiActionCheck, type AiStoredActionReview } from "./action-review";
|
|
@@ -14,6 +16,13 @@ import { aiTurnErrorText } from "./chat/turn-error";
|
|
|
14
16
|
import { AI_MEMORY_LEARNING_DEFAULT_ENABLED } from "./prefs";
|
|
15
17
|
import type { AiTurnBlock } from "./protocol";
|
|
16
18
|
import { collectRecentSentResourceRefs, RECENT_SENT_CONVERSATION_SCAN_LIMIT, RECENT_SENT_MESSAGE_SCAN_LIMIT } from "./recent-sent-refs";
|
|
19
|
+
import {
|
|
20
|
+
compactCapabilityReview,
|
|
21
|
+
compactReviewMapSql,
|
|
22
|
+
compactReviewMetaSql,
|
|
23
|
+
compactStoredReview,
|
|
24
|
+
storedReviewWithContent,
|
|
25
|
+
} from "./review-content";
|
|
17
26
|
import { AiRunTimeout } from "./run-timeout";
|
|
18
27
|
import { withAiShortId, withAiShortIdForDb } from "./short-id";
|
|
19
28
|
import { parseAiTodoPlan } from "./todo-contracts";
|
|
@@ -530,9 +539,9 @@ const parseJsonValue = <T>(value: unknown): T => {
|
|
|
530
539
|
return value as T;
|
|
531
540
|
};
|
|
532
541
|
|
|
533
|
-
const parseCapabilityActionReview = (value: unknown):
|
|
542
|
+
const parseCapabilityActionReview = (value: unknown): CapabilityActionReviewPresentation | undefined => {
|
|
534
543
|
if (value === null || value === undefined) return undefined;
|
|
535
|
-
const parsed =
|
|
544
|
+
const parsed = CapabilityActionReviewPresentationSchema.safeParse(restoreReviewSnapshot(parseJsonValue(value)));
|
|
536
545
|
return parsed.success ? parsed.data : undefined;
|
|
537
546
|
};
|
|
538
547
|
|
|
@@ -664,6 +673,14 @@ const intValue = (value: number | string | null | undefined): number => Math.max
|
|
|
664
673
|
|
|
665
674
|
const rowToMessage = (row: MessageRow): AiStoredMessage => {
|
|
666
675
|
const message = parseJsonValue<Message>(row.message);
|
|
676
|
+
const meta = row.meta ? parseJsonValue<AiStoredMessage["meta"]>(row.meta) : null;
|
|
677
|
+
if (meta?.toolReviews)
|
|
678
|
+
meta.toolReviews = Object.fromEntries(
|
|
679
|
+
Object.entries(meta.toolReviews).map(([callId, review]) => [
|
|
680
|
+
callId,
|
|
681
|
+
compactStoredReview(review, { conversationId: row.conversation_id, turnId: row.loop_id ?? "", callId }),
|
|
682
|
+
]),
|
|
683
|
+
);
|
|
667
684
|
return {
|
|
668
685
|
id: row.id,
|
|
669
686
|
shortId: row.short_id,
|
|
@@ -679,7 +696,7 @@ const rowToMessage = (row: MessageRow): AiStoredMessage => {
|
|
|
679
696
|
loopAggregate: row.loop_aggregate ? parseJsonValue<LoopAggregate>(row.loop_aggregate) : null,
|
|
680
697
|
loopDoneReason: row.loop_done_reason,
|
|
681
698
|
compactedAt: row.compacted_at ? iso(row.compacted_at) : null,
|
|
682
|
-
meta
|
|
699
|
+
meta,
|
|
683
700
|
feedback:
|
|
684
701
|
Number(row.feedback_rating) === 1 || Number(row.feedback_rating) === -1
|
|
685
702
|
? {
|
|
@@ -733,7 +750,16 @@ const pendingActionToPublicEvent = (row: PendingActionRow): AiPendingTurnAction
|
|
|
733
750
|
name: row.tool_name,
|
|
734
751
|
args: row.args,
|
|
735
752
|
message: row.message ?? undefined,
|
|
736
|
-
review:
|
|
753
|
+
review: (() => {
|
|
754
|
+
const review = parseCapabilityActionReview(row.review);
|
|
755
|
+
return review
|
|
756
|
+
? compactCapabilityReview(review, {
|
|
757
|
+
conversationId: row.conversation_id,
|
|
758
|
+
turnId: row.turn_id,
|
|
759
|
+
callId: row.kind === "custom_approval" ? row.call_id.replace(/-approval-[0-9]+$/, "") : row.call_id,
|
|
760
|
+
})
|
|
761
|
+
: undefined;
|
|
762
|
+
})(),
|
|
737
763
|
allowAlways: row.allow_always,
|
|
738
764
|
allowChat: row.allow_always || row.allow_chat === true,
|
|
739
765
|
...(row.remember_tool_name === AI_WEBSITE_APPROVAL_TOOL ? { website: row.approval_scope } : {}),
|
|
@@ -749,7 +775,16 @@ const rowToPendingActionRecord = (row: PendingActionRow): AiPendingTurnActionRec
|
|
|
749
775
|
name: row.tool_name,
|
|
750
776
|
args: row.args,
|
|
751
777
|
message: row.message ?? undefined,
|
|
752
|
-
review:
|
|
778
|
+
review: (() => {
|
|
779
|
+
const review = parseCapabilityActionReview(row.review);
|
|
780
|
+
return review
|
|
781
|
+
? compactCapabilityReview(review, {
|
|
782
|
+
conversationId: row.conversation_id,
|
|
783
|
+
turnId: row.turn_id,
|
|
784
|
+
callId: row.kind === "custom_approval" ? row.call_id.replace(/-approval-[0-9]+$/, "") : row.call_id,
|
|
785
|
+
})
|
|
786
|
+
: undefined;
|
|
787
|
+
})(),
|
|
753
788
|
approvalScope: row.approval_scope,
|
|
754
789
|
allowAlways: row.allow_always,
|
|
755
790
|
allowChat: row.allow_always || row.allow_chat === true,
|
|
@@ -994,7 +1029,7 @@ const recordAiToolMeta = async (
|
|
|
994
1029
|
UPDATE ${db(table)}
|
|
995
1030
|
SET meta = COALESCE(meta, '{}'::jsonb) || jsonb_build_object(
|
|
996
1031
|
${key}::text, COALESCE(meta->${key}::text, '{}'::jsonb) ||
|
|
997
|
-
jsonb_build_object(${input.callId}::text, ${JSON.stringify(value)}::text::jsonb)
|
|
1032
|
+
jsonb_build_object(${input.callId}::text, ${key === "toolReviews" ? serializeReviewSnapshot(value) : JSON.stringify(value)}::text::jsonb)
|
|
998
1033
|
)
|
|
999
1034
|
WHERE conversation_id = ${input.conversationId}::uuid AND loop_id = ${input.turnId}::text
|
|
1000
1035
|
AND role = 'assistant'
|
|
@@ -1005,8 +1040,16 @@ const recordAiToolMeta = async (
|
|
|
1005
1040
|
if (!written) throw new Error("AI tool call message is unavailable.");
|
|
1006
1041
|
};
|
|
1007
1042
|
|
|
1008
|
-
export const recordAiToolReview = (input: {
|
|
1009
|
-
|
|
1043
|
+
export const recordAiToolReview = async (input: {
|
|
1044
|
+
conversationId: string;
|
|
1045
|
+
turnId: string;
|
|
1046
|
+
callId: string;
|
|
1047
|
+
review: AiStoredActionReview;
|
|
1048
|
+
}): Promise<AiStoredActionReview> => {
|
|
1049
|
+
const stored = storedReviewWithContent(input.review, input);
|
|
1050
|
+
await recordAiToolMeta(input, "toolReviews", stored);
|
|
1051
|
+
return compactStoredReview(stored, input);
|
|
1052
|
+
};
|
|
1010
1053
|
|
|
1011
1054
|
export const recordAiToolCheck = (input: { conversationId: string; turnId: string; callId: string; check: AiActionCheck }, db = sql) =>
|
|
1012
1055
|
recordAiToolMeta(input, "toolChecks", input.check, db);
|
|
@@ -1319,11 +1362,45 @@ export const saveAiDraftInTransaction = async (
|
|
|
1319
1362
|
};
|
|
1320
1363
|
};
|
|
1321
1364
|
|
|
1365
|
+
export class AiConversationCreationError extends Error {
|
|
1366
|
+
constructor(readonly reason: "conflict" | "unavailable") {
|
|
1367
|
+
super(
|
|
1368
|
+
reason === "conflict"
|
|
1369
|
+
? "This creation request was already used with different input."
|
|
1370
|
+
: "The conversation from this request is no longer available.",
|
|
1371
|
+
);
|
|
1372
|
+
}
|
|
1373
|
+
}
|
|
1374
|
+
|
|
1322
1375
|
export const aiConversations: AiConversationService = {
|
|
1376
|
+
getDraftSubmission: async ({ conversationId, ownerUserId, revision }) => {
|
|
1377
|
+
const [row] = await sql<{ message_id: string; turn_id: string | null }[]>`
|
|
1378
|
+
SELECT message.short_id AS message_id, turn.short_id AS turn_id
|
|
1379
|
+
FROM ai.messages message
|
|
1380
|
+
JOIN ai.conversations conversation ON conversation.id = message.conversation_id
|
|
1381
|
+
LEFT JOIN ai.turns turn ON turn.id::text = message.loop_id AND turn.conversation_id = conversation.id
|
|
1382
|
+
WHERE conversation.id = ${conversationId}::uuid AND conversation.created_by_user_id = ${ownerUserId}::uuid
|
|
1383
|
+
AND conversation.archived_at IS NULL AND message.role = 'user'
|
|
1384
|
+
AND message.meta->>'submittedDraftRevision' = ${String(revision)}
|
|
1385
|
+
LIMIT 1
|
|
1386
|
+
`;
|
|
1387
|
+
return row ? { messageId: row.message_id, turnId: row.turn_id } : null;
|
|
1388
|
+
},
|
|
1323
1389
|
createConversation: async (input) => {
|
|
1324
|
-
const
|
|
1325
|
-
"
|
|
1326
|
-
(
|
|
1390
|
+
const normalized = {
|
|
1391
|
+
title: input.title?.trim() || "New chat",
|
|
1392
|
+
description: input.description?.trim() ?? "",
|
|
1393
|
+
projectId: input.projectId ?? null,
|
|
1394
|
+
draft: input.draft ?? [],
|
|
1395
|
+
preloadTools: input.preloadTools ?? [],
|
|
1396
|
+
allowedTools: input.allowedTools ?? null,
|
|
1397
|
+
launchedByAppId: input.launchedByAppId?.trim() || null,
|
|
1398
|
+
};
|
|
1399
|
+
const insert = async (db: SQL) => {
|
|
1400
|
+
const rows = await withAiShortIdForDb(
|
|
1401
|
+
db,
|
|
1402
|
+
"idx_ai_conversations_short_id",
|
|
1403
|
+
(attempt, shortId) => attempt<ConversationRow[]>`
|
|
1327
1404
|
INSERT INTO ai.conversations (
|
|
1328
1405
|
short_id,
|
|
1329
1406
|
title,
|
|
@@ -1339,8 +1416,8 @@ export const aiConversations: AiConversationService = {
|
|
|
1339
1416
|
)
|
|
1340
1417
|
SELECT
|
|
1341
1418
|
${shortId},
|
|
1342
|
-
${
|
|
1343
|
-
${
|
|
1419
|
+
${normalized.title},
|
|
1420
|
+
${normalized.description},
|
|
1344
1421
|
${input.projectId ?? null}::uuid,
|
|
1345
1422
|
${JSON.stringify(input.draft ?? [])}::text::jsonb,
|
|
1346
1423
|
${input.draft?.length ? 1 : 0},
|
|
@@ -1352,10 +1429,38 @@ export const aiConversations: AiConversationService = {
|
|
|
1352
1429
|
WHERE ${input.projectId ?? null}::uuid IS NULL
|
|
1353
1430
|
OR EXISTS (SELECT 1 FROM ai.projects project WHERE project.id = ${input.projectId ?? null}::uuid)
|
|
1354
1431
|
RETURNING *
|
|
1355
|
-
|
|
1356
|
-
|
|
1357
|
-
|
|
1358
|
-
|
|
1432
|
+
`,
|
|
1433
|
+
);
|
|
1434
|
+
if (!rows[0]) throw new Error("Project does not exist.");
|
|
1435
|
+
return rowToConversation(rows[0]);
|
|
1436
|
+
};
|
|
1437
|
+
if (!input.requestId) return insert(sql);
|
|
1438
|
+
const hash = capabilityRequestHash(normalized);
|
|
1439
|
+
return sql.begin(async (tx) => {
|
|
1440
|
+
const reserved = await tx<{ request_id: string }[]>`
|
|
1441
|
+
INSERT INTO ai.conversation_creations (owner_user_id, request_id, input_hash)
|
|
1442
|
+
VALUES (${input.ownerUserId}::uuid, ${input.requestId}::uuid, ${hash})
|
|
1443
|
+
ON CONFLICT (owner_user_id, request_id) DO NOTHING RETURNING request_id
|
|
1444
|
+
`;
|
|
1445
|
+
const [claim] = await tx<{ input_hash: string; conversation_id: string | null }[]>`
|
|
1446
|
+
SELECT input_hash, conversation_id FROM ai.conversation_creations
|
|
1447
|
+
WHERE owner_user_id = ${input.ownerUserId}::uuid AND request_id = ${input.requestId}::uuid
|
|
1448
|
+
FOR UPDATE
|
|
1449
|
+
`;
|
|
1450
|
+
if (!claim || claim.input_hash !== hash) throw new AiConversationCreationError("conflict");
|
|
1451
|
+
if (!reserved.length) {
|
|
1452
|
+
const [existing] = await tx<ConversationRow[]>`
|
|
1453
|
+
SELECT * FROM ai.conversations WHERE id = ${claim.conversation_id}::uuid
|
|
1454
|
+
AND created_by_user_id = ${input.ownerUserId}::uuid AND archived_at IS NULL
|
|
1455
|
+
`;
|
|
1456
|
+
if (!existing) throw new AiConversationCreationError("unavailable");
|
|
1457
|
+
return rowToConversation(existing);
|
|
1458
|
+
}
|
|
1459
|
+
const conversation = await insert(tx);
|
|
1460
|
+
await tx`UPDATE ai.conversation_creations SET conversation_id = ${conversation.id}::uuid
|
|
1461
|
+
WHERE owner_user_id = ${input.ownerUserId}::uuid AND request_id = ${input.requestId}::uuid`;
|
|
1462
|
+
return conversation;
|
|
1463
|
+
});
|
|
1359
1464
|
},
|
|
1360
1465
|
|
|
1361
1466
|
forkConversation: async (input) => {
|
|
@@ -2442,7 +2547,10 @@ export const aiConversations: AiConversationService = {
|
|
|
2442
2547
|
// summaries are hidden. The active summary sorts after archived rows
|
|
2443
2548
|
// sharing its checkpoint seq, marking where the model context begins.
|
|
2444
2549
|
const rows = await sql<MessageRow[]>`
|
|
2445
|
-
SELECT
|
|
2550
|
+
SELECT id, short_id, conversation_id, seq, kind, message, loop_id, model_profile_id, provider_model,
|
|
2551
|
+
usage, stop_reason, loop_aggregate, loop_done_reason, compacted_at, created_at,
|
|
2552
|
+
feedback_rating, feedback_reasons, feedback_comment, feedback_updated_at,
|
|
2553
|
+
${compactReviewMetaSql(todoMetaJson())} AS meta
|
|
2446
2554
|
FROM ai.messages
|
|
2447
2555
|
WHERE conversation_id = ${input.conversationId}
|
|
2448
2556
|
AND NOT (kind = 'summary' AND compacted_at IS NOT NULL)
|
|
@@ -2467,7 +2575,10 @@ export const aiConversations: AiConversationService = {
|
|
|
2467
2575
|
ORDER BY seq DESC
|
|
2468
2576
|
LIMIT ${limit}
|
|
2469
2577
|
)
|
|
2470
|
-
SELECT
|
|
2578
|
+
SELECT id, short_id, conversation_id, seq, kind, message, loop_id, model_profile_id, provider_model,
|
|
2579
|
+
usage, stop_reason, loop_aggregate, loop_done_reason, compacted_at, created_at,
|
|
2580
|
+
feedback_rating, feedback_reasons, feedback_comment, feedback_updated_at,
|
|
2581
|
+
${compactReviewMetaSql(todoMetaJson())} AS meta
|
|
2471
2582
|
FROM ai.messages
|
|
2472
2583
|
WHERE conversation_id = ${input.conversationId}
|
|
2473
2584
|
AND seq IN (SELECT seq FROM page_seqs)
|
|
@@ -2496,7 +2607,10 @@ export const aiConversations: AiConversationService = {
|
|
|
2496
2607
|
const pattern = `%${query.toLowerCase()}%`;
|
|
2497
2608
|
const rows = await sql<(MessageRow & { page_rank: number | string })[]>`
|
|
2498
2609
|
WITH matching AS (
|
|
2499
|
-
SELECT
|
|
2610
|
+
SELECT id, short_id, conversation_id, seq, kind, message, loop_id, model_profile_id, provider_model,
|
|
2611
|
+
usage, stop_reason, loop_aggregate, loop_done_reason, compacted_at, created_at,
|
|
2612
|
+
feedback_rating, feedback_reasons, feedback_comment, feedback_updated_at,
|
|
2613
|
+
${compactReviewMetaSql(todoMetaJson())} AS meta, DENSE_RANK() OVER (ORDER BY seq DESC) AS page_rank
|
|
2500
2614
|
FROM ai.messages
|
|
2501
2615
|
WHERE conversation_id = ${input.conversationId}::uuid
|
|
2502
2616
|
AND role <> 'tool_result'
|
|
@@ -2620,7 +2734,10 @@ export const aiConversations: AiConversationService = {
|
|
|
2620
2734
|
|
|
2621
2735
|
listContextMessages: async (input) => {
|
|
2622
2736
|
const rows = await sql<MessageRow[]>`
|
|
2623
|
-
SELECT
|
|
2737
|
+
SELECT id, short_id, conversation_id, seq, kind, message, loop_id, model_profile_id, provider_model,
|
|
2738
|
+
usage, stop_reason, loop_aggregate, loop_done_reason, compacted_at, created_at,
|
|
2739
|
+
feedback_rating, feedback_reasons, feedback_comment, feedback_updated_at,
|
|
2740
|
+
${compactReviewMetaSql(todoMetaJson())} AS meta
|
|
2624
2741
|
FROM ai.messages
|
|
2625
2742
|
WHERE conversation_id = ${input.conversationId}
|
|
2626
2743
|
AND compacted_at IS NULL
|
|
@@ -2631,7 +2748,10 @@ export const aiConversations: AiConversationService = {
|
|
|
2631
2748
|
|
|
2632
2749
|
listTurnMessages: async (input) => {
|
|
2633
2750
|
const rows = await sql<MessageRow[]>`
|
|
2634
|
-
SELECT
|
|
2751
|
+
SELECT id, short_id, conversation_id, seq, kind, message, loop_id, model_profile_id, provider_model,
|
|
2752
|
+
usage, stop_reason, loop_aggregate, loop_done_reason, compacted_at, created_at,
|
|
2753
|
+
feedback_rating, feedback_reasons, feedback_comment, feedback_updated_at,
|
|
2754
|
+
${compactReviewMetaSql(todoMetaJson())} AS meta
|
|
2635
2755
|
FROM (SELECT * FROM ai.messages UNION ALL SELECT * FROM ai.task_messages) messages
|
|
2636
2756
|
WHERE conversation_id = ${input.conversationId}
|
|
2637
2757
|
AND loop_id = ${input.loopId}
|
|
@@ -3042,7 +3162,7 @@ export const aiConversations: AiConversationService = {
|
|
|
3042
3162
|
const [waits, metadata] = await Promise.all([
|
|
3043
3163
|
activeTurnWaits(rows[0].id),
|
|
3044
3164
|
sql<{ tool_reviews: Record<string, AiStoredActionReview> | null; tool_checks: Record<string, AiActionCheck> | null }[]>`
|
|
3045
|
-
SELECT meta->'toolReviews' AS tool_reviews, meta->'toolChecks' AS tool_checks
|
|
3165
|
+
SELECT ${compactReviewMapSql(sql`meta->'toolReviews'`)} AS tool_reviews, meta->'toolChecks' AS tool_checks
|
|
3046
3166
|
FROM (
|
|
3047
3167
|
SELECT meta, seq, conversation_id, loop_id, role FROM ai.messages
|
|
3048
3168
|
UNION ALL
|
|
@@ -3062,7 +3182,8 @@ export const aiConversations: AiConversationService = {
|
|
|
3062
3182
|
turn: rowToTurn(rows[0]),
|
|
3063
3183
|
liveBlocks: rowToLiveBlocks(rows[0]).map((block) => {
|
|
3064
3184
|
if (block.kind !== "tool") return block;
|
|
3065
|
-
const
|
|
3185
|
+
const snapshot = reviews.get(block.callId);
|
|
3186
|
+
const review = snapshot ? compactStoredReview(snapshot, { ...input, turnId: rows[0]!.id, callId: block.callId }) : undefined;
|
|
3066
3187
|
const check = checks.get(block.callId);
|
|
3067
3188
|
return { ...block, ...(review ? { review } : {}), ...(check ? { check } : {}) };
|
|
3068
3189
|
}),
|
|
@@ -3546,7 +3667,7 @@ export const aiConversations: AiConversationService = {
|
|
|
3546
3667
|
${input.name},
|
|
3547
3668
|
${JSON.stringify(input.args ?? null)}::text::jsonb,
|
|
3548
3669
|
${input.message ?? null},
|
|
3549
|
-
${input.review ?
|
|
3670
|
+
${input.review ? serializeReviewSnapshot("sentence" in input.review ? storedReviewWithContent(input.review, input) : input.review) : null}::text::jsonb,
|
|
3550
3671
|
${input.approvalScope},
|
|
3551
3672
|
${input.allowAlways},
|
|
3552
3673
|
${input.allowChat ?? input.allowAlways},
|
|
@@ -3574,7 +3695,8 @@ export const aiConversations: AiConversationService = {
|
|
|
3574
3695
|
|
|
3575
3696
|
listPendingTurnActions: async (input) => {
|
|
3576
3697
|
const rows = await sql<PendingActionRow[]>`
|
|
3577
|
-
SELECT
|
|
3698
|
+
SELECT approval_id, turn_id, conversation_id, call_id, kind, status, tool_name, args, message,
|
|
3699
|
+
review - 'content' AS review, approval_scope, allow_always, allow_chat, remember_tool_name, frontend_mode, resolved_event
|
|
3578
3700
|
FROM ai.pending_actions
|
|
3579
3701
|
WHERE conversation_id = ${input.conversationId}
|
|
3580
3702
|
AND turn_id = ${input.turnId}
|
|
@@ -3586,7 +3708,8 @@ export const aiConversations: AiConversationService = {
|
|
|
3586
3708
|
|
|
3587
3709
|
getPendingTurnAction: async (input) => {
|
|
3588
3710
|
const rows = await sql<PendingActionRow[]>`
|
|
3589
|
-
SELECT
|
|
3711
|
+
SELECT approval_id, turn_id, conversation_id, call_id, kind, status, tool_name, args, message,
|
|
3712
|
+
review - 'content' AS review, approval_scope, allow_always, allow_chat, remember_tool_name, frontend_mode, resolved_event
|
|
3590
3713
|
FROM ai.pending_actions
|
|
3591
3714
|
WHERE conversation_id = ${input.conversationId}
|
|
3592
3715
|
AND turn_id = ${input.turnId}
|
|
@@ -3607,9 +3730,34 @@ export const aiConversations: AiConversationService = {
|
|
|
3607
3730
|
return row ? rowToPendingActionRecord(row) : null;
|
|
3608
3731
|
},
|
|
3609
3732
|
|
|
3733
|
+
listTurnToolReviews: async (input) => {
|
|
3734
|
+
// Only the review metadata, never the messages themselves, which can carry large tool results.
|
|
3735
|
+
const rows = await sql<{ tool_reviews: Record<string, AiStoredActionReview> | null }[]>`
|
|
3736
|
+
SELECT ${compactReviewMapSql(sql`meta->'toolReviews'`)} AS tool_reviews
|
|
3737
|
+
FROM (
|
|
3738
|
+
SELECT meta, seq, conversation_id, loop_id, role FROM ai.messages
|
|
3739
|
+
UNION ALL
|
|
3740
|
+
SELECT meta, seq, conversation_id, loop_id, role FROM ai.task_messages
|
|
3741
|
+
) messages
|
|
3742
|
+
WHERE conversation_id = ${input.conversationId}
|
|
3743
|
+
AND loop_id = ${input.turnId}
|
|
3744
|
+
AND role = 'assistant'
|
|
3745
|
+
AND meta ? 'toolReviews'
|
|
3746
|
+
ORDER BY seq ASC
|
|
3747
|
+
`;
|
|
3748
|
+
return new Map(
|
|
3749
|
+
rows.flatMap((row) =>
|
|
3750
|
+
Object.entries(row.tool_reviews ?? {}).map(
|
|
3751
|
+
([callId, review]) => [callId, compactStoredReview(review, { ...input, callId })] as const,
|
|
3752
|
+
),
|
|
3753
|
+
),
|
|
3754
|
+
);
|
|
3755
|
+
},
|
|
3756
|
+
|
|
3610
3757
|
listPendingActionRecords: async (input) => {
|
|
3611
3758
|
const rows = await sql<PendingActionRow[]>`
|
|
3612
|
-
SELECT
|
|
3759
|
+
SELECT approval_id, turn_id, conversation_id, call_id, kind, status, tool_name, args, message,
|
|
3760
|
+
review - 'content' AS review, approval_scope, allow_always, allow_chat, remember_tool_name, frontend_mode, resolved_event
|
|
3613
3761
|
FROM ai.pending_actions
|
|
3614
3762
|
WHERE conversation_id = ${input.conversationId}
|
|
3615
3763
|
AND turn_id = ${input.turnId}
|
|
@@ -3621,7 +3769,8 @@ export const aiConversations: AiConversationService = {
|
|
|
3621
3769
|
|
|
3622
3770
|
listResolvedPendingActions: async (input) => {
|
|
3623
3771
|
const rows = await sql<PendingActionRow[]>`
|
|
3624
|
-
SELECT
|
|
3772
|
+
SELECT approval_id, turn_id, conversation_id, call_id, kind, status, tool_name, args, message,
|
|
3773
|
+
review - 'content' AS review, approval_scope, allow_always, allow_chat, remember_tool_name, frontend_mode, resolved_event
|
|
3625
3774
|
FROM ai.pending_actions
|
|
3626
3775
|
WHERE conversation_id = ${input.conversationId}
|
|
3627
3776
|
AND turn_id = ${input.turnId}
|
|
@@ -3785,7 +3934,11 @@ export const aiConversations: AiConversationService = {
|
|
|
3785
3934
|
const rows = input.turnId
|
|
3786
3935
|
? (
|
|
3787
3936
|
await sql<MessageRow[]>`
|
|
3788
|
-
SELECT
|
|
3937
|
+
SELECT id, short_id, conversation_id, seq, kind, message, loop_id, model_profile_id, provider_model,
|
|
3938
|
+
usage, stop_reason, loop_aggregate, loop_done_reason, compacted_at, created_at,
|
|
3939
|
+
feedback_rating, feedback_reasons, feedback_comment, feedback_updated_at,
|
|
3940
|
+
${compactReviewMetaSql(todoMetaJson())} AS meta
|
|
3941
|
+
FROM ai.messages
|
|
3789
3942
|
WHERE conversation_id = ${input.conversationId}::uuid AND compacted_at IS NULL
|
|
3790
3943
|
AND ${visibleToTurn(input.turnId)}
|
|
3791
3944
|
ORDER BY seq ASC
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { actionFixtures } from "./chat/action-question.fixtures";
|
|
2
|
+
import { type AiOpenQuestion, openQuestionFromRow } from "./open-questions";
|
|
3
|
+
import type { AiTurnBlock } from "./protocol";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Demo Assistant questions for host tests and screenshots, in German and English, with fixed times. The demo data may
|
|
7
|
+
* change in any release: assert on what your host renders from it, not on its wording.
|
|
8
|
+
*/
|
|
9
|
+
export const aiQuestionFixtures = (locale: "de" | "en"): Record<string, { block: Extract<AiTurnBlock, { kind: "tool" }> }> =>
|
|
10
|
+
actionFixtures(locale);
|
|
11
|
+
|
|
12
|
+
/** A waiting question as `listAiOpenQuestions` presents it from the chat's tool block. */
|
|
13
|
+
export const aiOpenQuestionFixture = (
|
|
14
|
+
input: {
|
|
15
|
+
id: string;
|
|
16
|
+
conversationId: string;
|
|
17
|
+
conversationTitle: string | null;
|
|
18
|
+
createdAt: string;
|
|
19
|
+
block: Extract<AiTurnBlock, { kind: "tool" }>;
|
|
20
|
+
},
|
|
21
|
+
context: { locale: string; timeZone: string },
|
|
22
|
+
): AiOpenQuestion =>
|
|
23
|
+
openQuestionFromRow(
|
|
24
|
+
{
|
|
25
|
+
id: input.id,
|
|
26
|
+
conversationId: input.conversationId,
|
|
27
|
+
conversationTitle: input.conversationTitle,
|
|
28
|
+
displayCallId: input.block.callId,
|
|
29
|
+
appId: input.block.presentation?.kind === "capability" ? input.block.presentation.appId : "core",
|
|
30
|
+
sentence: "",
|
|
31
|
+
toolName: input.block.name,
|
|
32
|
+
createdAt: new Date(input.createdAt),
|
|
33
|
+
block: input.block,
|
|
34
|
+
},
|
|
35
|
+
context,
|
|
36
|
+
);
|
package/src/ai/tool-sentences.ts
CHANGED
|
@@ -1,38 +1,156 @@
|
|
|
1
|
-
import
|
|
1
|
+
import { actionReview } from "../capabilities/reviews";
|
|
2
|
+
import type { CapabilityActionCallReview } from "../contracts/capabilities";
|
|
3
|
+
import { CodeActionInput } from "./browser-code-contracts";
|
|
2
4
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
5
|
+
// Like the catalog these forms replace, code runs name no "not run" form: code asks about what it does inside the run,
|
|
6
|
+
// so a declined or stopped question does not mean the code did not run. No tool names a "rejected" form, so a declined
|
|
7
|
+
// call keeps Cloud's own words.
|
|
8
|
+
const forms = {
|
|
9
|
+
en: {
|
|
10
|
+
code_run: {
|
|
11
|
+
action: "Run code",
|
|
12
|
+
running: "Running code",
|
|
13
|
+
done: "Ran code",
|
|
14
|
+
confirm: "Run code",
|
|
15
|
+
decline: "Do not run",
|
|
16
|
+
},
|
|
17
|
+
code_action: {
|
|
18
|
+
action: "Run app action {action}",
|
|
19
|
+
running: "Running app action {action}",
|
|
20
|
+
done: "Ran app action {action}",
|
|
21
|
+
confirm: "Run action",
|
|
22
|
+
decline: "Do not run",
|
|
23
|
+
},
|
|
24
|
+
web_extract: {
|
|
25
|
+
action: "Read page on {site}",
|
|
26
|
+
running: "Reading page on {site}",
|
|
27
|
+
done: "Read page on {site}",
|
|
28
|
+
confirm: "Read page",
|
|
29
|
+
decline: "Do not read",
|
|
30
|
+
notRun: "Page on {site} not read",
|
|
31
|
+
},
|
|
32
|
+
fetch_file: {
|
|
33
|
+
action: "Download file from {site}",
|
|
34
|
+
running: "Downloading file from {site}",
|
|
35
|
+
done: "Downloaded file from {site}",
|
|
36
|
+
confirm: "Download file",
|
|
37
|
+
decline: "Do not download",
|
|
38
|
+
notRun: "File from {site} not downloaded",
|
|
39
|
+
},
|
|
40
|
+
write_file: {
|
|
41
|
+
action: "Write {file}",
|
|
42
|
+
running: "Writing {file}",
|
|
43
|
+
done: "Wrote {file}",
|
|
44
|
+
confirm: "Write file",
|
|
45
|
+
decline: "Do not write",
|
|
46
|
+
notRun: "{file} not written",
|
|
47
|
+
},
|
|
18
48
|
},
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
},
|
|
49
|
+
de: {
|
|
50
|
+
code_run: {
|
|
51
|
+
action: "Code ausführen",
|
|
52
|
+
running: "Code wird ausgeführt",
|
|
53
|
+
done: "Code ausgeführt",
|
|
54
|
+
confirm: "Code ausführen",
|
|
55
|
+
decline: "Nicht ausführen",
|
|
56
|
+
},
|
|
57
|
+
code_action: {
|
|
58
|
+
action: "App-Aktion {action} ausführen",
|
|
59
|
+
running: "App-Aktion {action} wird ausgeführt",
|
|
60
|
+
done: "App-Aktion {action} ausgeführt",
|
|
61
|
+
confirm: "Aktion ausführen",
|
|
62
|
+
decline: "Nicht ausführen",
|
|
63
|
+
},
|
|
64
|
+
web_extract: {
|
|
65
|
+
action: "Seite auf {site} lesen",
|
|
66
|
+
running: "Seite auf {site} wird gelesen",
|
|
67
|
+
done: "Seite auf {site} gelesen",
|
|
68
|
+
confirm: "Seite lesen",
|
|
69
|
+
decline: "Nicht lesen",
|
|
70
|
+
notRun: "Seite auf {site} nicht gelesen",
|
|
71
|
+
},
|
|
72
|
+
fetch_file: {
|
|
73
|
+
action: "Datei von {site} herunterladen",
|
|
74
|
+
running: "Datei von {site} wird heruntergeladen",
|
|
75
|
+
done: "Datei von {site} heruntergeladen",
|
|
76
|
+
confirm: "Datei herunterladen",
|
|
77
|
+
decline: "Nicht herunterladen",
|
|
78
|
+
notRun: "Datei von {site} nicht heruntergeladen",
|
|
79
|
+
},
|
|
80
|
+
write_file: {
|
|
81
|
+
action: "{file} schreiben",
|
|
82
|
+
running: "{file} wird geschrieben",
|
|
83
|
+
done: "{file} geschrieben",
|
|
84
|
+
confirm: "Datei schreiben",
|
|
85
|
+
decline: "Nicht schreiben",
|
|
86
|
+
notRun: "{file} nicht geschrieben",
|
|
36
87
|
},
|
|
37
88
|
},
|
|
38
|
-
} as const
|
|
89
|
+
} as const;
|
|
90
|
+
|
|
91
|
+
/** The bound of a `fields` preview value. */
|
|
92
|
+
const ADDRESS_MAX = 2000;
|
|
93
|
+
const readableAddress = (address: string): string | null => {
|
|
94
|
+
try {
|
|
95
|
+
return decodeURI(address);
|
|
96
|
+
} catch {
|
|
97
|
+
return null;
|
|
98
|
+
}
|
|
99
|
+
};
|
|
100
|
+
|
|
101
|
+
const isRecord = (value: unknown): value is Record<string, unknown> => value !== null && typeof value === "object" && !Array.isArray(value);
|
|
102
|
+
|
|
103
|
+
/** Cloud tools use the same per-call review contract as application Actions. Safe for historical, incomplete inputs. */
|
|
104
|
+
export const cloudToolReview = (name: string, args: unknown, locale: string): CapabilityActionCallReview | null => {
|
|
105
|
+
if (!isRecord(args)) return null;
|
|
106
|
+
const language = /^de(?:-|$)/i.test(locale) ? "de" : "en";
|
|
107
|
+
const input = args;
|
|
108
|
+
const sentence = forms[language];
|
|
109
|
+
try {
|
|
110
|
+
if (name === "code_run") {
|
|
111
|
+
return actionReview({ locale: language, sentence: sentence.code_run }, {});
|
|
112
|
+
}
|
|
113
|
+
if (name === "code_action") {
|
|
114
|
+
const parsed = CodeActionInput.shape.action.safeParse(input.action);
|
|
115
|
+
if (!parsed.success) return null;
|
|
116
|
+
return actionReview({ locale: language, sentence: sentence.code_action }, { action: { kind: "text", value: parsed.data } });
|
|
117
|
+
}
|
|
118
|
+
if (name === "web_extract" || name === "fetch_file") {
|
|
119
|
+
if (typeof input.url !== "string" || !input.url.trim()) return null;
|
|
120
|
+
const href = input.url;
|
|
121
|
+
const url = new URL(href);
|
|
122
|
+
if (!url.host) return null;
|
|
123
|
+
const label = language === "de" ? "Adresse" : "Address";
|
|
124
|
+
const review = (rows: { label: string; value: string }[]) =>
|
|
125
|
+
actionReview(
|
|
126
|
+
{ locale: language, sentence: sentence[name] },
|
|
127
|
+
// A place title holds 200 characters; the address rows keep the whole host.
|
|
128
|
+
{ site: { kind: "place", title: url.host.length > 200 ? `${url.host.slice(0, 99)}…${url.host.slice(-100)}` : url.host } },
|
|
129
|
+
{ preview: { kind: "fields", rows } },
|
|
130
|
+
);
|
|
131
|
+
// One address row holds at most 2,000 characters. An address whose encoded form is longer shows as written;
|
|
132
|
+
// if even that does not fit, the encoded address continues over numbered rows, so every character stays visible.
|
|
133
|
+
for (const address of [href, readableAddress(href)])
|
|
134
|
+
if (address && address.length <= ADDRESS_MAX) {
|
|
135
|
+
try {
|
|
136
|
+
return review([{ label, value: address }]);
|
|
137
|
+
} catch {
|
|
138
|
+
// Try the next form.
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
const parts = Array.from({ length: Math.ceil(href.length / ADDRESS_MAX) }, (_, index) =>
|
|
142
|
+
href.slice(index * ADDRESS_MAX, (index + 1) * ADDRESS_MAX),
|
|
143
|
+
);
|
|
144
|
+
return review(parts.map((value, index) => ({ label: `${label} ${index + 1}/${parts.length}`, value })));
|
|
145
|
+
}
|
|
146
|
+
if (name === "write_file") {
|
|
147
|
+
if (typeof input.path !== "string" || !input.path.trim()) return null;
|
|
148
|
+
const file = input.path.trim().split("/").at(-1);
|
|
149
|
+
if (!file) return null;
|
|
150
|
+
return actionReview({ locale: language, sentence: sentence.write_file }, { file: { kind: "object", title: file.slice(0, 200) } });
|
|
151
|
+
}
|
|
152
|
+
} catch {
|
|
153
|
+
// Historical calls can predate the current bounds or carry partial input; they keep their generic tool title.
|
|
154
|
+
}
|
|
155
|
+
return null;
|
|
156
|
+
};
|