@k2b/cloud 0.45.0 → 0.47.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 +11 -4
- package/scripts/build.ts +5 -1
- package/scripts/preload.ts +5 -1
- package/src/_internal/app-queries.ts +73 -0
- package/src/_internal/capabilities.ts +137 -9
- package/src/_internal/define-app.ts +40 -0
- package/src/_internal/heartbeat.ts +19 -0
- package/src/_internal/postgres-application-name.ts +11 -0
- package/src/_internal/process-sync.ts +9 -0
- package/src/_internal/readiness.ts +6 -1
- package/src/_internal/registry-validation.ts +17 -4
- package/src/_internal/registry.ts +32 -4
- package/src/_internal/resource-principals.ts +6 -0
- package/src/_internal/workload-credential.ts +153 -0
- package/src/_internal/workload-protocol.ts +36 -0
- package/src/ai/application-tasks.ts +5 -2
- package/src/ai/capabilities.ts +4 -2
- package/src/ai/chart-block.ts +36 -711
- package/src/ai/chat/map-messages.ts +270 -0
- package/src/ai/chat/map-notebook.browser-harness.tsx +28 -0
- package/src/ai/chat/map-notebook.tsx +301 -0
- package/src/ai/chat/map-widget.browser-harness.tsx +71 -0
- package/src/ai/chat/map-widget.css +107 -0
- package/src/ai/chat/map-widget.tsx +1279 -0
- package/src/ai/chat/message-utils.ts +1 -0
- package/src/ai/chat/tool-names.ts +2 -0
- package/src/ai/chat/turn-layout.ts +2 -1
- package/src/ai/chat/turn-view.tsx +17 -1
- package/src/ai/default-tool-contracts.ts +21 -259
- package/src/ai/default-tools.ts +3 -0
- package/src/ai/executor.ts +13 -1
- package/src/ai/map-contracts.ts +209 -0
- package/src/ai/map-execution.ts +14 -0
- package/src/ai/map-notebook-save.ts +276 -0
- package/src/ai/map-notebook.ts +32 -0
- package/src/ai/map-opening-hours.ts +40 -0
- package/src/ai/map-routes.ts +84 -0
- package/src/ai/map-service.ts +288 -0
- package/src/ai/map-store.ts +137 -0
- package/src/ai/map-tool.ts +73 -0
- package/src/ai/migrate.ts +3 -0
- package/src/ai/protocol.ts +3 -1
- package/src/ai/routes.ts +2 -0
- package/src/ai/store.ts +19 -6
- package/src/ai/tools.ts +14 -0
- package/src/ai/types.ts +3 -0
- package/src/api/admin-app-keys.ts +66 -0
- package/src/api/admin-core-settings.ts +2 -0
- package/src/api/admin-identity.ts +27 -1
- package/src/api/admin-settings-env.ts +120 -0
- package/src/api/capabilities.ts +99 -15
- package/src/api/index.ts +3 -0
- package/src/api/mcp.ts +4 -2
- package/src/api/settings-ops.ts +35 -0
- package/src/app-key.ts +125 -0
- package/src/browser/CloudResourceSearch.tsx +48 -41
- package/src/browser/notifications.ts +25 -8
- package/src/capabilities/discussion-providers.ts +100 -0
- package/src/capabilities/executions.ts +2 -2
- package/src/capabilities/migrate.ts +10 -1
- package/src/capabilities/resource-readers.ts +69 -0
- package/src/capabilities/response.ts +4 -1
- package/src/capabilities/server-invoke.ts +64 -36
- package/src/capabilities/server.ts +17 -6
- package/src/capabilities/testing.ts +13 -0
- package/src/capabilities/types.ts +1 -1
- package/src/cli/admin/app-credentials.ts +32 -7
- package/src/cli/admin/app-keys.ts +226 -0
- package/src/cli/admin/index.ts +7 -1
- package/src/cli/admin/settings.ts +62 -0
- package/src/config/define-env.ts +63 -8
- package/src/config/env.ts +27 -21
- package/src/config/index.ts +12 -1
- package/src/config/workflow-ai.ts +28 -0
- package/src/contracts/capabilities.ts +137 -4
- package/src/contracts/discussions.ts +91 -0
- package/src/contracts/geo-document.ts +60 -0
- package/src/contracts/geo-snapshot.ts +86 -0
- package/src/contracts/geo.ts +14 -0
- package/src/contracts/index.ts +2 -0
- package/src/contracts/outgoing-mail.ts +1 -1
- package/src/contracts/resource-types.ts +21 -0
- package/src/contracts/settings-env.ts +21 -0
- package/src/contracts/settings-types.ts +6 -0
- package/src/geo/AddressAutocomplete.tsx +213 -0
- package/src/geo/messages.ts +29 -0
- package/src/geo/navigation.ts +25 -0
- package/src/geo/ui.ts +2 -0
- package/src/index.ts +9 -0
- package/src/runtime/clients.ts +72 -0
- package/src/runtime/connections.ts +37 -0
- package/src/runtime/resolve-connections.ts +91 -0
- package/src/server/client-ip.ts +1 -1
- package/src/services/accounts/app.ts +28 -6
- package/src/services/accounts/entities.ts +11 -4
- package/src/services/accounts/groups.ts +10 -4
- package/src/services/accounts/local-groups.ts +87 -38
- package/src/services/accounts/machine-group-management.ts +94 -0
- package/src/services/app-keys.ts +298 -0
- package/src/services/gateway.ts +6 -0
- package/src/services/geo/cache.ts +2 -1
- package/src/services/geo/geoapify.ts +3 -0
- package/src/services/identity/index.ts +3 -0
- package/src/services/identity/invocation-token.ts +110 -0
- package/src/services/identity/resource-readers.ts +80 -0
- package/src/services/identity/runtime-config.ts +14 -6
- package/src/services/identity/workload-runtime.ts +253 -0
- package/src/services/index.ts +4 -1
- package/src/services/outgoing-mail/resolve-host.ts +5 -4
- package/src/services/pdf/geo-snapshot.ts +107 -0
- package/src/services/pdf/index.ts +2 -0
- package/src/services/service-account-credentials.ts +63 -21
- package/src/services/settings/api.ts +3 -3
- package/src/services/settings/binding.ts +45 -0
- package/src/services/settings/core-settings.ts +99 -1
- package/src/services/settings/defaults.ts +16 -2
- package/src/services/settings/env-status.ts +69 -0
- package/src/services/settings/index.ts +69 -26
- package/src/services/settings/store.ts +73 -8
- package/src/services/settings/validation.ts +40 -0
- package/src/shared/index.ts +11 -10
- package/src/shared/markdown/extensions/tables.ts +10 -1
- package/src/ssr/AdminArea.tsx +2 -2
- package/src/ssr/AdminLayout.tsx +1 -1
- package/src/ssr/AdminSidebar.tsx +1 -1
- package/src/start.ts +36 -0
- package/src/styles/global.css +2 -0
- package/src/workflows/ai/index.ts +1 -0
- package/src/workflows/ai/runtime.ts +16 -8
- package/src/workflows/ai/settings.ts +5 -0
- package/src/shared/markdown/formula.ts +0 -1283
|
@@ -327,6 +327,7 @@ export const displayToolName = (name: string, locale: string) => {
|
|
|
327
327
|
|
|
328
328
|
const BUILT_IN_TOOL_ICONS = new Map<string, string>([
|
|
329
329
|
["card", "ti ti-layout-cards"],
|
|
330
|
+
["map", "ti ti-map-search"],
|
|
330
331
|
["cloud_card", "ti ti-layout-cards"],
|
|
331
332
|
["chart", "ti ti-chart-bar"],
|
|
332
333
|
["survey", "ti ti-forms"],
|
|
@@ -10,6 +10,7 @@ const toolNames = i18n.define({
|
|
|
10
10
|
en: {
|
|
11
11
|
calculate: "Calculate",
|
|
12
12
|
card: "Card",
|
|
13
|
+
map: "Places and maps",
|
|
13
14
|
chart: "Chart",
|
|
14
15
|
survey: "Survey",
|
|
15
16
|
text_editor: "Text editor",
|
|
@@ -86,6 +87,7 @@ const toolNames = i18n.define({
|
|
|
86
87
|
de: {
|
|
87
88
|
calculate: "Rechnen",
|
|
88
89
|
card: "Karte",
|
|
90
|
+
map: "Orte und Karten",
|
|
89
91
|
chart: "Diagramm",
|
|
90
92
|
survey: "Umfrage",
|
|
91
93
|
text_editor: "Texteditor",
|
|
@@ -70,11 +70,12 @@ const resultTarget = (block: ToolBlock): string | null => {
|
|
|
70
70
|
};
|
|
71
71
|
|
|
72
72
|
const isResult = (block: ToolBlock, phase: AiTurnPhase, codePresentations: boolean): boolean => {
|
|
73
|
+
if (block.name === "map" && block.result && typeof block.result === "object" && "map" in block.result) return true;
|
|
73
74
|
if (isFailedTool(block) || block.status === "rejected" || block.status === "awaiting_approval") return false;
|
|
74
75
|
// A running delivery reserves its place once its arguments arrived: a new version of an earlier result must find the
|
|
75
76
|
// earlier place before it takes one of its own.
|
|
76
77
|
const delivered = block.status === "completed" || (live(phase) && block.status === "running" && block.args !== undefined);
|
|
77
|
-
if (block.name === "present" || isCardToolName(block.name) || isChartToolName(block.name)) return delivered;
|
|
78
|
+
if (block.name === "map" || block.name === "present" || isCardToolName(block.name) || isChartToolName(block.name)) return delivered;
|
|
78
79
|
if (block.name === "code_present") return codePresentations && delivered;
|
|
79
80
|
return hasCapabilityTable(block);
|
|
80
81
|
};
|
|
@@ -23,6 +23,7 @@ import { CompactionBlockView, CompactToolRow, SurveyToolView, TextEditorToolView
|
|
|
23
23
|
import { CapabilityTablePreview } from "./capability-table";
|
|
24
24
|
import { CloudChartBlock } from "./chart-block";
|
|
25
25
|
import { PresentToolBlock } from "./file-tools";
|
|
26
|
+
import { AiMapToolBlock } from "./map-widget";
|
|
26
27
|
import { useAiChatActions } from "./message-actions";
|
|
27
28
|
import { aiToolIcon, displayToolName, isCardToolName, isChartToolName, isRecord, isSurveyToolName } from "./message-utils";
|
|
28
29
|
import { aiChatMessages } from "./messages";
|
|
@@ -438,6 +439,9 @@ function AiTurnResultView(props: { result: Accessor<AiTurnResult | undefined>; r
|
|
|
438
439
|
<Show when={block()}>
|
|
439
440
|
{(current) => (
|
|
440
441
|
<Switch>
|
|
442
|
+
<Match when={current().name === "map"}>
|
|
443
|
+
<AiMapToolBlock block={current()} />
|
|
444
|
+
</Match>
|
|
441
445
|
<Match when={current().name === "present"}>
|
|
442
446
|
<PresentToolBlock block={current()} />
|
|
443
447
|
</Match>
|
|
@@ -653,7 +657,18 @@ export function AiTurnView(props: { segment: Accessor<AiTurnSegment>; disclosure
|
|
|
653
657
|
const chatActions = useAiChatActions();
|
|
654
658
|
const layout = () => props.segment().layout;
|
|
655
659
|
const results = createMemo(() => new Map(layout().results.map((result) => [result.id, result])));
|
|
656
|
-
const
|
|
660
|
+
const isMapFollowup = (id: string) => {
|
|
661
|
+
const block = results().get(id)?.block;
|
|
662
|
+
return (
|
|
663
|
+
block?.name === "map" &&
|
|
664
|
+
typeof block.result === "object" &&
|
|
665
|
+
block.result !== null &&
|
|
666
|
+
"anchor" in block.result &&
|
|
667
|
+
block.result.anchor === false
|
|
668
|
+
);
|
|
669
|
+
};
|
|
670
|
+
const resultIds = createMemo(() => [...results().keys()].filter((id) => !isMapFollowup(id)));
|
|
671
|
+
const mapFollowupIds = createMemo(() => [...results().keys()].filter(isMapFollowup));
|
|
657
672
|
const actions = createMemo(() => new Map(layout().actions.map((action) => [action.id, action])));
|
|
658
673
|
const actionIds = createMemo(() => [...actions().keys()]);
|
|
659
674
|
const live = () => isLive(props.segment().phase);
|
|
@@ -709,6 +724,7 @@ export function AiTurnView(props: { segment: Accessor<AiTurnSegment>; disclosure
|
|
|
709
724
|
earlier={() => props.segment().earlier}
|
|
710
725
|
/>
|
|
711
726
|
</Show>
|
|
727
|
+
<For each={mapFollowupIds()}>{(id) => <AiTurnResultView result={() => results().get(id)} receipt={() => false} />}</For>
|
|
712
728
|
<Show when={props.segment().scheduledTask?.stop ? undefined : props.segment().scheduledTask?.reason}>
|
|
713
729
|
{(reason) => (
|
|
714
730
|
<Chat.Activity
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
|
-
import { chartDataIssue,
|
|
2
|
+
import { chartDataIssue, chartDrawingIssue, cloudAiChartMarks } from "./chart-block";
|
|
3
3
|
|
|
4
4
|
const ToneSchema = z.enum(["neutral", "blue", "teal", "green", "amber", "red"]);
|
|
5
5
|
|
|
@@ -28,261 +28,30 @@ export const CLOUD_AI_VIEW_MAX_MARKS = CLOUD_AI_CHART_MAX_SERIES * CLOUD_AI_CHAR
|
|
|
28
28
|
*/
|
|
29
29
|
export const CLOUD_AI_GROUP_MAX_CHARTS = 6;
|
|
30
30
|
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
// Intl.NumberFormat supports this precision; table formats already use 0..10.
|
|
44
|
-
decimals: z.number().int().min(0).max(10).optional(),
|
|
45
|
-
compact: z.boolean().optional(),
|
|
46
|
-
})
|
|
47
|
-
.superRefine((value, ctx) => {
|
|
48
|
-
if ((value.style === "currency") !== (value.currency !== undefined))
|
|
49
|
-
ctx.addIssue({ code: "custom", path: ["currency"], message: "Currency is required exactly when style is currency." });
|
|
50
|
-
});
|
|
51
|
-
const dateFormatSchema = () => z.strictObject({ date: z.enum(["day", "minute", "second"]) });
|
|
52
|
-
export const cloudAiFormatSchema = () => z.union([cloudAiNumberFormatSchema(), dateFormatSchema()]);
|
|
53
|
-
export type CloudAiNumberFormat = z.output<ReturnType<typeof cloudAiNumberFormatSchema>>;
|
|
54
|
-
export type CloudAiDateFormat = z.output<ReturnType<typeof dateFormatSchema>>;
|
|
55
|
-
export type CloudAiFormat = CloudAiNumberFormat | CloudAiDateFormat;
|
|
31
|
+
import {
|
|
32
|
+
type ChartInput as CloudAiChartInput,
|
|
33
|
+
type ChartDateFormat as CloudAiDateFormat,
|
|
34
|
+
type ChartFormat as CloudAiFormat,
|
|
35
|
+
type ChartNumberFormat as CloudAiNumberFormat,
|
|
36
|
+
chartKinds,
|
|
37
|
+
chartParts,
|
|
38
|
+
chartSchema,
|
|
39
|
+
chartKindSchema as cloudAiChartKindSchema,
|
|
40
|
+
chartFormatSchema as cloudAiFormatSchema,
|
|
41
|
+
chartNumberFormatSchema as cloudAiNumberFormatSchema,
|
|
42
|
+
} from "@k2b/editor/charts";
|
|
56
43
|
|
|
57
|
-
export
|
|
58
|
-
|
|
44
|
+
export type { CloudAiChartInput, CloudAiDateFormat, CloudAiFormat, CloudAiNumberFormat };
|
|
45
|
+
// Portable formats and chart types retain their existing Cloud export names.
|
|
46
|
+
export { cloudAiChartKindSchema, cloudAiFormatSchema, cloudAiNumberFormatSchema };
|
|
59
47
|
|
|
60
|
-
const
|
|
61
|
-
const ChartLabel = limits ? z.string().trim().min(1).max(80) : z.union([z.string(), z.number().transform(String)]);
|
|
62
|
-
const ChartTooltip = z
|
|
63
|
-
.strictObject({ label: ChartLabel.optional(), format: cloudAiNumberFormatSchema().optional() })
|
|
64
|
-
.describe("All kinds: what tooltips and the data table call the value and how they format it; axes keep their own format.");
|
|
65
|
-
// Missing observations stay numeric so the renderer can omit them with its finite-value checks.
|
|
66
|
-
const ChartNumber = limits
|
|
67
|
-
? z.number()
|
|
68
|
-
: z
|
|
69
|
-
.custom<number>((value) => typeof value === "number", "Expected a number, null or undefined.")
|
|
70
|
-
.nullable()
|
|
71
|
-
.optional()
|
|
72
|
-
.transform((value) => value ?? Number.NaN);
|
|
73
|
-
const list = <T extends z.ZodType>(item: T, min: number, max?: number, message?: string) => {
|
|
74
|
-
const array = z.array(item);
|
|
75
|
-
const bounded = limits && min > 0 ? array.min(min) : array;
|
|
76
|
-
return limits && max !== undefined ? bounded.max(max, message) : bounded;
|
|
77
|
-
};
|
|
78
|
-
const ChartDate = z
|
|
79
|
-
.string()
|
|
80
|
-
.regex(/^\d{4}-\d{2}-\d{2}(?:T\d{2}:\d{2}(?::\d{2})?)?$/)
|
|
81
|
-
.refine((value) => chartDateTime(value) !== undefined, "Not a valid date.");
|
|
82
|
-
const ChartPoint = z.object({
|
|
83
|
-
x: z.union([ChartNumber, ChartDate]).describe("line, scatter: number or YYYY-MM-DD[THH:mm[:ss]], wall-clock time without an offset."),
|
|
84
|
-
y: ChartNumber.describe("line, scatter: value."),
|
|
85
|
-
});
|
|
86
|
-
// As many lines as series a reader can tell apart.
|
|
87
|
-
const ChartReferences = list(
|
|
88
|
-
z.object({ value: z.union([z.number(), ChartDate]), axis: z.enum(["x", "y"]).optional(), label: ChartLabel.optional() }),
|
|
89
|
-
0,
|
|
90
|
-
CLOUD_AI_CHART_MAX_SERIES,
|
|
91
|
-
`Use at most ${CLOUD_AI_CHART_MAX_SERIES} references.`,
|
|
92
|
-
);
|
|
93
|
-
// One palette color per band.
|
|
94
|
-
const ChartThresholds = list(
|
|
95
|
-
z.object({ value: z.number(), label: ChartLabel.optional() }),
|
|
96
|
-
1,
|
|
97
|
-
CLOUD_AI_CHART_MAX_SERIES,
|
|
98
|
-
`Use at most ${CLOUD_AI_CHART_MAX_SERIES} thresholds.`,
|
|
99
|
-
);
|
|
100
|
-
return {
|
|
101
|
-
ChartLabel,
|
|
102
|
-
ChartTooltip,
|
|
103
|
-
ChartNumber,
|
|
104
|
-
list,
|
|
105
|
-
ChartReferences,
|
|
106
|
-
ChartThresholds,
|
|
107
|
-
ChartAxis: z.object({
|
|
108
|
-
label: ChartLabel.optional(),
|
|
109
|
-
domain: z.array(z.number()).length(2).optional().describe("Exact [min, max]; must contain every value and reference."),
|
|
110
|
-
ticks: (limits ? z.number().int().min(2).max(12) : z.number().int().min(2)).optional(),
|
|
111
|
-
scale: z.enum(["linear", "log"]).optional().describe("log needs values above zero."),
|
|
112
|
-
format: cloudAiFormatSchema().optional(),
|
|
113
|
-
}),
|
|
114
|
-
ChartCategory: z.object({ label: ChartLabel, value: ChartNumber }),
|
|
115
|
-
ChartSlice: z.object({ label: ChartLabel, value: limits ? z.number().min(0) : ChartNumber }),
|
|
116
|
-
ChartSeries: (points: z.ZodArray<typeof ChartPoint>) =>
|
|
117
|
-
z.object({
|
|
118
|
-
label: ChartLabel.optional(),
|
|
119
|
-
data: points,
|
|
120
|
-
role: z.literal("reference").optional(),
|
|
121
|
-
}),
|
|
122
|
-
ChartPoints: list(ChartPoint, 0, CLOUD_AI_CHART_MAX_VALUES),
|
|
123
|
-
ChartBins: z
|
|
124
|
-
.union([
|
|
125
|
-
limits ? z.number().int().min(1).max(CLOUD_AI_CHART_MAX_VALUES) : z.number().int().min(1),
|
|
126
|
-
limits
|
|
127
|
-
? z
|
|
128
|
-
.array(z.number())
|
|
129
|
-
.min(2)
|
|
130
|
-
.max(CLOUD_AI_CHART_MAX_VALUES + 1)
|
|
131
|
-
: z.array(z.number()).min(2),
|
|
132
|
-
])
|
|
133
|
-
.describe("histogram: a bin count or ascending bin edges."),
|
|
134
|
-
ChartCommon: {
|
|
135
|
-
title: (limits ? z.string().trim().min(1).max(160) : z.string()).describe("All kinds and groups: chart title."),
|
|
136
|
-
subtitle: (limits ? z.string().trim().min(1).max(300) : z.string())
|
|
137
|
-
.optional()
|
|
138
|
-
.describe("All kinds and groups: unit, period or source."),
|
|
139
|
-
},
|
|
140
|
-
ChartUnit: limits ? z.string().trim().min(1).max(12) : z.string(),
|
|
141
|
-
ChartBox: z.object({ label: ChartLabel, values: list(ChartNumber, 1) }),
|
|
142
|
-
ChartCell: z.object({ x: ChartLabel, y: ChartLabel, value: ChartNumber }),
|
|
143
|
-
ChartBarGauge: z.object({ label: ChartLabel, value: ChartNumber, min: z.number().optional(), max: z.number().optional() }),
|
|
144
|
-
};
|
|
145
|
-
};
|
|
146
|
-
|
|
147
|
-
const chartKinds = (limits = true) => {
|
|
148
|
-
const {
|
|
149
|
-
ChartNumber,
|
|
150
|
-
list,
|
|
151
|
-
ChartAxis,
|
|
152
|
-
ChartBins,
|
|
153
|
-
ChartCategory,
|
|
154
|
-
ChartCommon,
|
|
155
|
-
ChartTooltip,
|
|
156
|
-
ChartLabel,
|
|
157
|
-
ChartPoints,
|
|
158
|
-
ChartSeries,
|
|
159
|
-
ChartSlice,
|
|
160
|
-
ChartReferences,
|
|
161
|
-
ChartThresholds,
|
|
162
|
-
ChartUnit,
|
|
163
|
-
ChartBox,
|
|
164
|
-
ChartCell,
|
|
165
|
-
ChartBarGauge,
|
|
166
|
-
} = chartParts(limits);
|
|
167
|
-
const ChartKindCommon = { ...ChartCommon, tooltip: ChartTooltip.optional() };
|
|
168
|
-
const numberFormat = cloudAiNumberFormatSchema();
|
|
169
|
-
return z.discriminatedUnion("kind", [
|
|
170
|
-
z.object({
|
|
171
|
-
kind: z.literal("bar"),
|
|
172
|
-
...ChartKindCommon,
|
|
173
|
-
data: list(ChartCategory, 1, CLOUD_AI_CHART_MAX_VALUES),
|
|
174
|
-
xAxis: ChartAxis.pick({ label: true }).strict().optional(),
|
|
175
|
-
yAxis: ChartAxis.optional(),
|
|
176
|
-
colorByBar: z.boolean().optional(),
|
|
177
|
-
showValues: z.boolean().optional(),
|
|
178
|
-
legend: z.boolean().optional(),
|
|
179
|
-
references: ChartReferences.optional(),
|
|
180
|
-
}),
|
|
181
|
-
z.object({
|
|
182
|
-
kind: z.literal("line"),
|
|
183
|
-
...ChartKindCommon,
|
|
184
|
-
series: list(
|
|
185
|
-
ChartSeries(
|
|
186
|
-
limits
|
|
187
|
-
? ChartPoints.min(2, "A line needs at least two points per series; show single values as a bar or scatter chart.")
|
|
188
|
-
: ChartPoints,
|
|
189
|
-
).extend({ style: z.enum(["solid", "dashed", "dotted"]).optional() }),
|
|
190
|
-
1,
|
|
191
|
-
CLOUD_AI_CHART_MAX_SERIES,
|
|
192
|
-
),
|
|
193
|
-
xAxis: ChartAxis.optional(),
|
|
194
|
-
yAxis: ChartAxis.optional(),
|
|
195
|
-
legend: z.boolean().optional(),
|
|
196
|
-
area: z.boolean().optional(),
|
|
197
|
-
smooth: z.boolean().optional(),
|
|
198
|
-
references: ChartReferences.optional(),
|
|
199
|
-
}),
|
|
200
|
-
z.object({
|
|
201
|
-
kind: z.literal("scatter"),
|
|
202
|
-
...ChartKindCommon,
|
|
203
|
-
series: list(ChartSeries(limits ? ChartPoints.min(1) : ChartPoints), 1, CLOUD_AI_CHART_MAX_SERIES),
|
|
204
|
-
xAxis: ChartAxis.optional(),
|
|
205
|
-
yAxis: ChartAxis.optional(),
|
|
206
|
-
legend: z.boolean().optional(),
|
|
207
|
-
references: ChartReferences.optional(),
|
|
208
|
-
}),
|
|
209
|
-
z.object({
|
|
210
|
-
kind: z.enum(["pie", "donut"]),
|
|
211
|
-
...ChartKindCommon,
|
|
212
|
-
data: list(ChartSlice, 1, CLOUD_AI_CHART_MAX_VALUES),
|
|
213
|
-
legend: z.boolean().optional(),
|
|
214
|
-
showLabels: z.boolean().optional(),
|
|
215
|
-
format: numberFormat.optional(),
|
|
216
|
-
}),
|
|
217
|
-
z.object({
|
|
218
|
-
kind: z.literal("histogram"),
|
|
219
|
-
...ChartKindCommon,
|
|
220
|
-
data: list(ChartNumber, 1, CLOUD_AI_CHART_MAX_VALUES),
|
|
221
|
-
bins: ChartBins.optional(),
|
|
222
|
-
references: ChartReferences.optional(),
|
|
223
|
-
}),
|
|
224
|
-
z.object({
|
|
225
|
-
kind: z.literal("gauge"),
|
|
226
|
-
...ChartKindCommon,
|
|
227
|
-
value: ChartNumber,
|
|
228
|
-
min: z.number().optional(),
|
|
229
|
-
max: z.number().optional(),
|
|
230
|
-
label: ChartLabel.optional(),
|
|
231
|
-
unit: ChartUnit.optional(),
|
|
232
|
-
format: numberFormat.optional(),
|
|
233
|
-
thresholds: ChartThresholds.optional(),
|
|
234
|
-
}),
|
|
235
|
-
z.object({
|
|
236
|
-
kind: z.literal("sparkline"),
|
|
237
|
-
...ChartKindCommon,
|
|
238
|
-
data: list(ChartNumber, 2, CLOUD_AI_CHART_MAX_VALUES),
|
|
239
|
-
area: z.boolean().optional(),
|
|
240
|
-
}),
|
|
241
|
-
z.object({
|
|
242
|
-
kind: z.literal("boxplot"),
|
|
243
|
-
...ChartKindCommon,
|
|
244
|
-
data: list(ChartBox, 1, CLOUD_AI_CHART_MAX_VALUES),
|
|
245
|
-
yAxis: ChartAxis.optional(),
|
|
246
|
-
references: ChartReferences.optional(),
|
|
247
|
-
}),
|
|
248
|
-
z.object({
|
|
249
|
-
kind: z.literal("heatmap"),
|
|
250
|
-
...ChartKindCommon,
|
|
251
|
-
data: list(ChartCell, 1, CLOUD_AI_VIEW_MAX_MARKS),
|
|
252
|
-
format: numberFormat.optional(),
|
|
253
|
-
showValues: z.boolean().optional(),
|
|
254
|
-
min: z.number().optional(),
|
|
255
|
-
max: z.number().optional(),
|
|
256
|
-
}),
|
|
257
|
-
z.object({
|
|
258
|
-
kind: z.literal("barGauge"),
|
|
259
|
-
...ChartKindCommon,
|
|
260
|
-
// The shared renderer draws 280 units high and a bar gauge row is 28 units, so 8 rows fit without a taller drawing.
|
|
261
|
-
data: list(ChartBarGauge, 1, CLOUD_AI_CHART_MAX_SERIES, `Use at most ${CLOUD_AI_CHART_MAX_SERIES} bar gauge items.`),
|
|
262
|
-
min: z.number().optional(),
|
|
263
|
-
max: z.number().optional(),
|
|
264
|
-
unit: ChartUnit.optional(),
|
|
265
|
-
format: numberFormat.optional(),
|
|
266
|
-
thresholds: ChartThresholds.optional(),
|
|
267
|
-
}),
|
|
268
|
-
z.object({
|
|
269
|
-
kind: z.literal("stat"),
|
|
270
|
-
...ChartKindCommon,
|
|
271
|
-
value: ChartNumber,
|
|
272
|
-
format: numberFormat.optional(),
|
|
273
|
-
unit: ChartUnit.optional(),
|
|
274
|
-
delta: limits ? z.number().optional() : ChartNumber,
|
|
275
|
-
better: z.enum(["up", "down"]).optional(),
|
|
276
|
-
sparkline: list(ChartNumber, 2, CLOUD_AI_CHART_MAX_VALUES).optional(),
|
|
277
|
-
}),
|
|
278
|
-
]);
|
|
279
|
-
};
|
|
48
|
+
const chartLimits = { maxSeries: CLOUD_AI_CHART_MAX_SERIES, maxValues: CLOUD_AI_CHART_MAX_VALUES, maxMarks: CLOUD_AI_VIEW_MAX_MARKS };
|
|
280
49
|
|
|
281
50
|
/** Checks run on complete input only, so out-of-range input fails on its bounds before anything is computed or drawn. */
|
|
282
51
|
const whenValid = { when: (payload: { issues: readonly unknown[] }) => payload.issues.length === 0 };
|
|
283
52
|
|
|
284
53
|
export const cloudAiChartSchema = () =>
|
|
285
|
-
chartKinds().superRefine((chart, context) => {
|
|
54
|
+
chartKinds(chartLimits).superRefine((chart, context) => {
|
|
286
55
|
const total = cloudAiChartMarks(chart);
|
|
287
56
|
const issue =
|
|
288
57
|
total > CLOUD_AI_VIEW_MAX_MARKS
|
|
@@ -291,16 +60,9 @@ export const cloudAiChartSchema = () =>
|
|
|
291
60
|
if (issue) context.addIssue({ code: "custom", message: issue });
|
|
292
61
|
}, whenValid);
|
|
293
62
|
/** Apps retain the renderer's permissive data contract, except values that would stall tick generation. */
|
|
294
|
-
export const cloudAiAppChartSchema =
|
|
295
|
-
chartKinds(false).superRefine((chart, context) => {
|
|
296
|
-
const issue = chartSteppingIssue(chart);
|
|
297
|
-
if (issue) context.addIssue({ code: "custom", message: issue });
|
|
298
|
-
}, whenValid);
|
|
63
|
+
export const cloudAiAppChartSchema = chartSchema;
|
|
299
64
|
const ChartSchema = /* @__PURE__ */ cloudAiChartSchema();
|
|
300
65
|
|
|
301
|
-
/** One chart as the renderers read it: a `cloud.chart()` kind with its options. */
|
|
302
|
-
export type CloudAiChartInput = z.output<ReturnType<typeof chartKinds>>;
|
|
303
|
-
|
|
304
66
|
/**
|
|
305
67
|
* Reads one chart without drawing it; undefined when it is not a chart. Writers have drawn it once already, so
|
|
306
68
|
* renderers draw only for display.
|
|
@@ -311,7 +73,7 @@ export const parseCloudAiChartInput = (value: unknown): CloudAiChartInput | unde
|
|
|
311
73
|
};
|
|
312
74
|
|
|
313
75
|
export const cloudAiGroupSchema = () => {
|
|
314
|
-
const { ChartCommon } = chartParts();
|
|
76
|
+
const { ChartCommon } = chartParts(chartLimits);
|
|
315
77
|
return z
|
|
316
78
|
.strictObject({
|
|
317
79
|
...ChartCommon,
|
|
@@ -354,7 +116,7 @@ const flatChartSchema = () => {
|
|
|
354
116
|
ChartThresholds,
|
|
355
117
|
ChartUnit,
|
|
356
118
|
ChartTooltip,
|
|
357
|
-
} = chartParts();
|
|
119
|
+
} = chartParts(chartLimits);
|
|
358
120
|
return z.object({
|
|
359
121
|
kind: cloudAiChartKindSchema().optional().describe("Single charts: kind. Omit for groups."),
|
|
360
122
|
...ChartCommon,
|
package/src/ai/default-tools.ts
CHANGED
|
@@ -56,6 +56,7 @@ import {
|
|
|
56
56
|
CloudAiLocalWriteInputSchema,
|
|
57
57
|
CloudAiLocalWriteOutputSchema,
|
|
58
58
|
} from "./local-tool-contracts";
|
|
59
|
+
import { createCloudAiMapTool } from "./map-tool";
|
|
59
60
|
import { createCloudAiMarkdownToPdfTool } from "./markdown-pdf-tool";
|
|
60
61
|
import { createAiTodoTool } from "./todo-tool";
|
|
61
62
|
import { defineAiTool } from "./tools";
|
|
@@ -303,6 +304,7 @@ export const CLOUD_AI_DEFERRED_BUILTIN_TOOL_NAMES = new Set<string>([
|
|
|
303
304
|
"markdown_to_pdf",
|
|
304
305
|
"html_to_pdf",
|
|
305
306
|
"present",
|
|
307
|
+
"map",
|
|
306
308
|
"read_cloud_resource",
|
|
307
309
|
]);
|
|
308
310
|
|
|
@@ -314,6 +316,7 @@ export const createConfiguredDefaultCloudAiTools = async (config?: {
|
|
|
314
316
|
}) => {
|
|
315
317
|
const tools: AiRuntimeTool[] = [
|
|
316
318
|
...createDefaultCloudAiTools(),
|
|
319
|
+
createCloudAiMapTool(),
|
|
317
320
|
...createCodeSourceTools(),
|
|
318
321
|
createCloudAiListFilesTool(),
|
|
319
322
|
createCloudAiReadFileTool(),
|
package/src/ai/executor.ts
CHANGED
|
@@ -547,7 +547,7 @@ const createEventMapper = (
|
|
|
547
547
|
args: "args" in patch ? patch.args : existing?.args,
|
|
548
548
|
status: patch.status ?? existing?.status ?? "running",
|
|
549
549
|
at: patch.at ?? existing?.at,
|
|
550
|
-
result: "result" in patch ? patch.result : existing?.result,
|
|
550
|
+
result: patch.isError && existing?.result !== undefined ? existing.result : "result" in patch ? patch.result : existing?.result,
|
|
551
551
|
isError: "isError" in patch ? patch.isError : existing?.isError,
|
|
552
552
|
approval: approval && !allowRememberedApprovals ? { ...approval, allowAlways: false, allowChat: false } : approval,
|
|
553
553
|
review: patch.review ?? existing?.review,
|
|
@@ -1080,6 +1080,11 @@ export class AiTurnExecutor {
|
|
|
1080
1080
|
const dynamicToolRuntimeContext = {
|
|
1081
1081
|
turnId,
|
|
1082
1082
|
reportToolProgress: (callId: string, message: string) => pipeline.reportToolProgress(callId, message),
|
|
1083
|
+
reportToolResult: (callId: string, result: unknown) => pipeline.reportToolResult(callId, result),
|
|
1084
|
+
maxToolResultChars: resolveAiToolResultMaxChars({
|
|
1085
|
+
contextWindow: resolved.provider.contextWindow,
|
|
1086
|
+
configuredMaxChars: settings.maxToolResultChars,
|
|
1087
|
+
}),
|
|
1083
1088
|
reportWebsiteReceipts: (callId: string, receipts: AiWebsiteReceipt[]) => pipeline.reportWebsiteReceipts(callId, receipts),
|
|
1084
1089
|
localToolsEnabled: activeTools.some((tool) => tool.def.name === "local_upload"),
|
|
1085
1090
|
attachedFilePaths: new Set(config.files?.attached.map((file) => file.path) ?? []),
|
|
@@ -2501,6 +2506,13 @@ class StreamPipeline {
|
|
|
2501
2506
|
await this.maybeSnapshot();
|
|
2502
2507
|
}
|
|
2503
2508
|
|
|
2509
|
+
async reportToolResult(callId: string, result: unknown): Promise<void> {
|
|
2510
|
+
const block = this.blocks.find((block) => block.kind === "tool" && block.callId === callId);
|
|
2511
|
+
if (!block || block.kind !== "tool" || block.status !== "running") return;
|
|
2512
|
+
await this.emitOp(this.mapper.setTool(callId, { result }));
|
|
2513
|
+
await this.maybeSnapshot();
|
|
2514
|
+
}
|
|
2515
|
+
|
|
2504
2516
|
async reportToolProgress(callId: string, progress: string): Promise<void> {
|
|
2505
2517
|
const block = this.blocks.find((block) => block.kind === "tool" && block.callId === callId);
|
|
2506
2518
|
if (!block || block.kind !== "tool" || block.progress === progress) return;
|
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import {
|
|
3
|
+
GeoAddressSchema,
|
|
4
|
+
GeoAvailabilityDataSchema,
|
|
5
|
+
GeoCategorySchema,
|
|
6
|
+
GeoCostsSchema,
|
|
7
|
+
GeoPlaceSchema,
|
|
8
|
+
GeoPlaceSearchInputSchema,
|
|
9
|
+
GeoPointSchema,
|
|
10
|
+
} from "../contracts/geo";
|
|
11
|
+
import {
|
|
12
|
+
GeoRouteChangesSchema,
|
|
13
|
+
GeoRouteOptionsSchema,
|
|
14
|
+
GeoRoutePlanSchema,
|
|
15
|
+
GeoRouteSchema,
|
|
16
|
+
GeoRouteUpdateDataSchema,
|
|
17
|
+
} from "../contracts/geo-route";
|
|
18
|
+
|
|
19
|
+
/** Provider website text is optional and is not guaranteed to be a valid URL. */
|
|
20
|
+
export const aiMapWebsite = (value: string | undefined): string | undefined => {
|
|
21
|
+
try {
|
|
22
|
+
const url = new URL(value ?? "");
|
|
23
|
+
return url.protocol === "https:" || url.protocol === "http:" ? value : undefined;
|
|
24
|
+
} catch {
|
|
25
|
+
return undefined;
|
|
26
|
+
}
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
const label = z.string().trim().min(1).max(160);
|
|
30
|
+
const web = z.object({ placeId: z.string().max(2048), description: z.string().max(600), url: z.url().max(2000) }).strict();
|
|
31
|
+
// A map may accumulate two full Places result pages. Subsequent searches replace the secondary group.
|
|
32
|
+
export const AiMapSchema = z
|
|
33
|
+
.object({
|
|
34
|
+
id: z.uuid(),
|
|
35
|
+
conversationId: z.string(),
|
|
36
|
+
revision: z.number().int().positive(),
|
|
37
|
+
title: label,
|
|
38
|
+
selectionId: z.string().optional(),
|
|
39
|
+
timeZone: z.string().optional(),
|
|
40
|
+
kind: z.enum(["places", "route"]),
|
|
41
|
+
pending: z.boolean().optional(),
|
|
42
|
+
pendingTurnId: z.uuid().optional(),
|
|
43
|
+
retryUsesMarks: z.boolean().optional(),
|
|
44
|
+
originCallId: z.string().optional(),
|
|
45
|
+
anchorSeq: z.number().int().positive().optional(),
|
|
46
|
+
viewedAt: z.iso.datetime().optional(),
|
|
47
|
+
lastSearch: z
|
|
48
|
+
.object({
|
|
49
|
+
categories: z.array(GeoCategorySchema),
|
|
50
|
+
name: GeoPlaceSearchInputSchema.shape.name,
|
|
51
|
+
near: z.string().optional(),
|
|
52
|
+
placeId: z.string().optional(),
|
|
53
|
+
radiusMeters: z.number().optional(),
|
|
54
|
+
title: label,
|
|
55
|
+
along: z.object({ fromKm: z.number(), toKm: z.number(), bufferMeters: z.number().optional() }).optional(),
|
|
56
|
+
})
|
|
57
|
+
.optional(),
|
|
58
|
+
searchAddress: z.object({ text: z.string(), address: GeoAddressSchema }).strict().optional(),
|
|
59
|
+
availability: GeoAvailabilityDataSchema,
|
|
60
|
+
groups: z.array(z.object({ title: label, places: z.array(GeoPlaceSchema).max(20) }).strict()).max(2),
|
|
61
|
+
web: z.array(web).max(40),
|
|
62
|
+
route: GeoRouteSchema.optional(),
|
|
63
|
+
plan: GeoRoutePlanSchema.optional(),
|
|
64
|
+
preview: z.object({ result: GeoRouteUpdateDataSchema, changes: GeoRouteChangesSchema, title: label }).strict().optional(),
|
|
65
|
+
undo: z.object({ route: GeoRouteSchema, title: label }).strict().optional(),
|
|
66
|
+
receipt: z.object({ title: label, at: z.iso.datetime() }).strict().optional(),
|
|
67
|
+
candidates: z.array(GeoAddressSchema).max(5).optional(),
|
|
68
|
+
ambiguousStopId: z.string().optional(),
|
|
69
|
+
picker: z.array(GeoPlaceSchema).max(5).optional(),
|
|
70
|
+
retryInsertStopId: z.uuid().optional(),
|
|
71
|
+
issue: z
|
|
72
|
+
.object({
|
|
73
|
+
code: z.string(),
|
|
74
|
+
scope: z.enum(["person", "cloud"]).optional(),
|
|
75
|
+
resetsAt: z.string().optional(),
|
|
76
|
+
retryAfterSeconds: z.number().optional(),
|
|
77
|
+
costs: GeoCostsSchema.optional(),
|
|
78
|
+
requiredCredits: z.number().nonnegative().optional(),
|
|
79
|
+
availableCredits: z.number().optional(),
|
|
80
|
+
turnId: z.uuid().optional(),
|
|
81
|
+
requiresCostConfirmation: z.literal(true).optional(),
|
|
82
|
+
shortfallCredits: z.number().nonnegative().optional(),
|
|
83
|
+
})
|
|
84
|
+
.strict()
|
|
85
|
+
.optional(),
|
|
86
|
+
})
|
|
87
|
+
.strict();
|
|
88
|
+
export type AiMap = z.infer<typeof AiMapSchema>;
|
|
89
|
+
|
|
90
|
+
const search = z
|
|
91
|
+
.object({
|
|
92
|
+
categories: z.array(GeoCategorySchema).min(1).max(5),
|
|
93
|
+
name: GeoPlaceSearchInputSchema.shape.name,
|
|
94
|
+
near: z.string().min(1).max(300).optional(),
|
|
95
|
+
placeId: z.string().max(2048).optional(),
|
|
96
|
+
radiusMeters: z.number().min(1).max(50000).optional(),
|
|
97
|
+
along: z
|
|
98
|
+
.object({ fromKm: z.number().nonnegative(), toKm: z.number().positive(), bufferMeters: z.number().min(100).max(5000).optional() })
|
|
99
|
+
.strict()
|
|
100
|
+
.optional(),
|
|
101
|
+
title: label,
|
|
102
|
+
})
|
|
103
|
+
.strict();
|
|
104
|
+
// Model-facing inputs cannot inject points, polylines, or a provider result. These are resolved on the server.
|
|
105
|
+
const stop = z
|
|
106
|
+
.object({
|
|
107
|
+
id: z.string().min(1).max(40),
|
|
108
|
+
address: z.string().min(1).max(300).optional(),
|
|
109
|
+
placeId: z.string().max(2048).optional(),
|
|
110
|
+
label: label.optional(),
|
|
111
|
+
})
|
|
112
|
+
.strict()
|
|
113
|
+
.refine((value) => Boolean(value.address) !== Boolean(value.placeId), "Supply exactly one address or verified place ID");
|
|
114
|
+
export const AiMapCommandSchema = z.discriminatedUnion("type", [
|
|
115
|
+
z.object({ type: z.literal("places"), search, web: z.array(web).max(20).optional() }).strict(),
|
|
116
|
+
z
|
|
117
|
+
.object({
|
|
118
|
+
type: z.literal("route"),
|
|
119
|
+
stops: z
|
|
120
|
+
.array(stop)
|
|
121
|
+
.min(2)
|
|
122
|
+
.max(26)
|
|
123
|
+
.refine((stops) => new Set(stops.map((s) => s.id)).size === stops.length, "Stop IDs must be unique"),
|
|
124
|
+
options: GeoRouteOptionsSchema.optional(),
|
|
125
|
+
title: label,
|
|
126
|
+
})
|
|
127
|
+
.strict(),
|
|
128
|
+
z.object({ type: z.literal("search"), search, insert: z.boolean().optional(), web: z.array(web).max(20).optional() }).strict(),
|
|
129
|
+
z.object({ type: z.literal("describe"), web: z.array(web).max(40) }).strict(),
|
|
130
|
+
z
|
|
131
|
+
.object({
|
|
132
|
+
type: z.literal("preview"),
|
|
133
|
+
change: z.discriminatedUnion("type", [
|
|
134
|
+
z.object({ type: z.literal("insert"), placeId: z.string().max(2048), afterStopId: z.string().optional() }).strict(),
|
|
135
|
+
z.object({ type: z.literal("move"), stopId: z.string(), toIndex: z.number().int().min(0).max(25) }).strict(),
|
|
136
|
+
z.object({ type: z.literal("optimize") }).strict(),
|
|
137
|
+
z
|
|
138
|
+
.object({
|
|
139
|
+
type: z.literal("options"),
|
|
140
|
+
options: GeoRouteOptionsSchema,
|
|
141
|
+
leg: z.object({ fromStopId: z.string(), toStopId: z.string() }).strict().optional(),
|
|
142
|
+
})
|
|
143
|
+
.strict(),
|
|
144
|
+
]),
|
|
145
|
+
})
|
|
146
|
+
.strict(),
|
|
147
|
+
z.object({ type: z.literal("choose"), index: z.number().int().min(0).max(4) }).strict(),
|
|
148
|
+
z.object({ type: z.literal("accept") }).strict(),
|
|
149
|
+
z.object({ type: z.literal("discard") }).strict(),
|
|
150
|
+
z.object({ type: z.literal("undo") }).strict(),
|
|
151
|
+
z.object({ type: z.literal("retry") }).strict(),
|
|
152
|
+
]);
|
|
153
|
+
export const AiMapBrowserCommandSchema = z.union([
|
|
154
|
+
AiMapCommandSchema,
|
|
155
|
+
z.object({ type: z.literal("location"), destinationId: z.string(), point: GeoPointSchema }).strict(),
|
|
156
|
+
z
|
|
157
|
+
.object({ type: z.literal("repair"), stopId: z.string(), address: z.string().trim().min(1).max(300), point: GeoPointSchema.optional() })
|
|
158
|
+
.strict(),
|
|
159
|
+
]);
|
|
160
|
+
export type AiMapCommand = z.infer<typeof AiMapBrowserCommandSchema>;
|
|
161
|
+
export const AiMapToolInputSchema = z
|
|
162
|
+
.object({ mapId: z.uuid().optional(), revision: z.number().int().positive().optional(), command: AiMapCommandSchema })
|
|
163
|
+
.strict();
|
|
164
|
+
export const AiMapReferenceSchema = AiMapSchema.pick({
|
|
165
|
+
id: true,
|
|
166
|
+
conversationId: true,
|
|
167
|
+
revision: true,
|
|
168
|
+
title: true,
|
|
169
|
+
kind: true,
|
|
170
|
+
pending: true,
|
|
171
|
+
anchorSeq: true,
|
|
172
|
+
}).extend({ reference: z.literal(true) });
|
|
173
|
+
export const AiMapToolResultSchema = z
|
|
174
|
+
.object({ map: z.union([AiMapSchema, AiMapReferenceSchema]), anchor: z.boolean(), summary: z.unknown().optional() })
|
|
175
|
+
.strict();
|
|
176
|
+
/** Geometry belongs to the durable map, never to the model context or streaming result budget. */
|
|
177
|
+
export const aiMapToolResult = (map: AiMap, anchor: boolean): z.infer<typeof AiMapToolResultSchema> => ({
|
|
178
|
+
anchor,
|
|
179
|
+
map: {
|
|
180
|
+
reference: true,
|
|
181
|
+
id: map.id,
|
|
182
|
+
conversationId: map.conversationId,
|
|
183
|
+
revision: map.revision,
|
|
184
|
+
title: map.title,
|
|
185
|
+
kind: map.kind,
|
|
186
|
+
pending: map.pending,
|
|
187
|
+
anchorSeq: map.anchorSeq,
|
|
188
|
+
},
|
|
189
|
+
summary: {
|
|
190
|
+
mapId: map.id,
|
|
191
|
+
revision: map.revision,
|
|
192
|
+
places: map.groups.map((g) => ({
|
|
193
|
+
title: g.title.slice(0, 64),
|
|
194
|
+
places: g.places.map((p, i) => ({
|
|
195
|
+
id: map.groups.indexOf(g) === 0 ? String(i + 1) : String.fromCharCode(65 + i),
|
|
196
|
+
name: p.name.slice(0, 48),
|
|
197
|
+
})),
|
|
198
|
+
})),
|
|
199
|
+
candidates: map.candidates?.map((p, index) => ({ index, address: p.formatted.slice(0, 96) })),
|
|
200
|
+
picker: map.picker?.map((p, i) => ({ id: `pick-${i + 1}`, name: p.name.slice(0, 48), atMeters: p.atMeters })),
|
|
201
|
+
stops: map.route?.stops.map((s) => ({ id: s.id, label: s.label?.slice(0, 48) })),
|
|
202
|
+
distanceMeters: map.route?.distanceMeters,
|
|
203
|
+
durationSeconds: map.route?.durationSeconds,
|
|
204
|
+
preview: map.preview ? { title: map.preview.title, delta: map.preview.result.delta } : undefined,
|
|
205
|
+
issue: map.issue,
|
|
206
|
+
},
|
|
207
|
+
});
|
|
208
|
+
export type AiMapToolResult = z.infer<typeof AiMapToolResultSchema>;
|
|
209
|
+
export const AiMapActionSchema = z.object({ revision: z.number().int().positive(), command: AiMapBrowserCommandSchema }).strict();
|