@k2b/cloud 0.30.0 → 0.32.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.
Files changed (171) hide show
  1. package/package.json +7 -4
  2. package/src/_internal/app-presentation.ts +22 -1
  3. package/src/_internal/capabilities.ts +2 -2
  4. package/src/_internal/define-app.ts +17 -5
  5. package/src/_internal/registry.ts +50 -6
  6. package/src/_internal/widget-declarations.ts +53 -0
  7. package/src/ai/app-skills.ts +1 -7
  8. package/src/ai/approval-routes.ts +40 -4
  9. package/src/ai/approvals.ts +68 -10
  10. package/src/ai/audio-tool.ts +2 -1
  11. package/src/ai/browser.ts +109 -0
  12. package/src/ai/capabilities.ts +6 -2
  13. package/src/ai/chart-block.ts +431 -81
  14. package/src/ai/chat/action-sentences.ts +13 -7
  15. package/src/ai/chat/blocks.tsx +64 -39
  16. package/src/ai/chat/builtin-tools.tsx +9 -7
  17. package/src/ai/chat/chart-block.tsx +218 -44
  18. package/src/ai/chat/explorer-block.tsx +276 -0
  19. package/src/ai/chat/file-tools.tsx +40 -1
  20. package/src/ai/chat/live-turn.browser-harness.tsx +4 -1
  21. package/src/ai/chat/message-actions.tsx +9 -1
  22. package/src/ai/chat/message-utils.ts +7 -0
  23. package/src/ai/chat/messages.ts +34 -0
  24. package/src/ai/chat/presentation.tsx +13 -3
  25. package/src/ai/chat/reference-text.tsx +34 -0
  26. package/src/ai/chat/tool-groups.ts +67 -12
  27. package/src/ai/chat/tool-names.ts +18 -2
  28. package/src/ai/chat/turn-layout.ts +8 -0
  29. package/src/ai/chat/turn-view.tsx +111 -32
  30. package/src/ai/chat/view-block.browser-harness.tsx +35 -0
  31. package/src/ai/chat/view-block.tsx +417 -0
  32. package/src/ai/chat/visual-tools.tsx +32 -53
  33. package/src/ai/chat/web-tools.tsx +95 -1
  34. package/src/ai/client/controller.ts +6 -3
  35. package/src/ai/client-tool-results.ts +9 -0
  36. package/src/ai/code-runtime-tools.ts +50 -17
  37. package/src/ai/code-source-contracts.ts +25 -21
  38. package/src/ai/code-source-tools.ts +3 -1
  39. package/src/ai/default-tool-contracts.ts +334 -91
  40. package/src/ai/default-tools.ts +87 -11
  41. package/src/ai/executor.ts +343 -45
  42. package/src/ai/fetch-file-tool.ts +41 -4
  43. package/src/ai/file-error.ts +2 -0
  44. package/src/ai/file-tools.ts +27 -7
  45. package/src/ai/files-store.ts +60 -0
  46. package/src/ai/firecrawl-tools.ts +87 -4
  47. package/src/ai/html-pdf-tool.ts +3 -2
  48. package/src/ai/http.ts +37 -12
  49. package/src/ai/index.ts +98 -0
  50. package/src/ai/local-tool-contracts.ts +244 -0
  51. package/src/ai/malformed-tool-call-repair.ts +144 -0
  52. package/src/ai/markdown-pdf-tool.ts +2 -1
  53. package/src/ai/migrate.ts +120 -2
  54. package/src/ai/openai-api-messages.ts +53 -0
  55. package/src/ai/openai-api-slots.ts +40 -0
  56. package/src/ai/openai-api.ts +383 -0
  57. package/src/ai/protocol.ts +28 -3
  58. package/src/ai/provider.ts +16 -4
  59. package/src/ai/request-context.ts +33 -0
  60. package/src/ai/routes.ts +99 -49
  61. package/src/ai/runtime.ts +90 -15
  62. package/src/ai/settings.ts +5 -0
  63. package/src/ai/skill-catalog.ts +20 -5
  64. package/src/ai/skill-format.ts +17 -7
  65. package/src/ai/skill-tool.ts +15 -5
  66. package/src/ai/skills-routes.ts +20 -6
  67. package/src/ai/skills.ts +26 -7
  68. package/src/ai/store.ts +91 -1
  69. package/src/ai/system-prompt.ts +69 -10
  70. package/src/ai/timeline.ts +3 -1
  71. package/src/ai/tool-audit.ts +11 -1
  72. package/src/ai/tools.ts +94 -3
  73. package/src/ai/types.ts +48 -0
  74. package/src/ai/view-block.ts +941 -0
  75. package/src/ai/vision-tool.ts +2 -1
  76. package/src/ai/web-provenance.ts +103 -0
  77. package/src/api/admin-identity.ts +6 -1
  78. package/src/api/app-approval-actions.ts +36 -0
  79. package/src/api/app-approval.ts +10 -2
  80. package/src/api/approvals-loader.ts +15 -0
  81. package/src/api/approvals.ts +120 -0
  82. package/src/api/capabilities.ts +3 -2
  83. package/src/api/index.ts +3 -0
  84. package/src/api/me.ts +12 -3
  85. package/src/api/widgets.ts +36 -5
  86. package/src/approvals/Approvals.browser-harness.tsx +16 -0
  87. package/src/approvals/Approvals.tsx +621 -0
  88. package/src/approvals/ApprovalsNavStatus.tsx +20 -0
  89. package/src/approvals/messages.ts +95 -0
  90. package/src/approvals/ui.ts +8 -0
  91. package/src/browser/CloudResourceSearch.tsx +1 -1
  92. package/src/browser/app-approval.ts +32 -0
  93. package/src/browser/choose-files.tsx +26 -13
  94. package/src/browser/command-shortcuts.ts +21 -6
  95. package/src/browser/commands.ts +13 -4
  96. package/src/browser/file-provider-error.ts +11 -0
  97. package/src/browser/file-providers.ts +3 -12
  98. package/src/browser/files.tsx +1 -2
  99. package/src/browser/live-connection.ts +2 -3
  100. package/src/browser/provider-list.ts +2 -1
  101. package/src/browser/save-files.tsx +13 -2
  102. package/src/browser/spotlight-position.ts +4 -1
  103. package/src/browser/widgets.ts +7 -5
  104. package/src/capabilities/executions.ts +3 -0
  105. package/src/cli/account.ts +21 -2
  106. package/src/cli/commands.ts +24 -2
  107. package/src/cli/index.ts +16 -0
  108. package/src/cli/plugin.ts +30 -0
  109. package/src/contracts/app-approval.ts +120 -0
  110. package/src/contracts/app.ts +34 -2
  111. package/src/contracts/approval-paths.ts +4 -0
  112. package/src/contracts/approvals.ts +81 -0
  113. package/src/contracts/command-path.ts +17 -0
  114. package/src/contracts/commands.ts +5 -15
  115. package/src/contracts/registry.ts +9 -1
  116. package/src/contracts/shared.ts +5 -0
  117. package/src/contracts/widgets.ts +47 -2
  118. package/src/desktop/solid.tsx +6 -7
  119. package/src/events/live-protocol.ts +2 -1
  120. package/src/events/live-server-message.ts +40 -0
  121. package/src/events/testing.ts +88 -0
  122. package/src/server/index.ts +3 -0
  123. package/src/server/middleware/auth.ts +11 -1
  124. package/src/server/request-timeout.ts +22 -0
  125. package/src/server/services/access.ts +4 -3
  126. package/src/server/widget-request.ts +27 -0
  127. package/src/services/app-approval.ts +103 -15
  128. package/src/services/approvals/cursor.ts +15 -0
  129. package/src/services/approvals/device.ts +112 -0
  130. package/src/services/approvals/index.ts +241 -0
  131. package/src/services/approvals/list.ts +120 -0
  132. package/src/services/approvals/migrate.ts +41 -0
  133. package/src/services/identity/models-only-auth.ts +25 -0
  134. package/src/services/notifications/email-frame.ts +16 -3
  135. package/src/services/service-account-credentials.ts +22 -10
  136. package/src/services/settings/core-settings.ts +27 -0
  137. package/src/shared/ai-platform-prompt.ts +26 -1
  138. package/src/shared/app-presentation.ts +11 -0
  139. package/src/shared/email-html.ts +46 -7
  140. package/src/shared/index.ts +6 -1
  141. package/src/shared/markdown/extensions/code.ts +14 -0
  142. package/src/shared/markdown/index.ts +4 -4
  143. package/src/ssr/AdminLayout.tsx +4 -7
  144. package/src/ssr/AppBadge.tsx +4 -4
  145. package/src/ssr/AppLaunchpad.island.tsx +1 -1
  146. package/src/ssr/AppLaunchpadPanel.tsx +4 -4
  147. package/src/ssr/Layout.tsx +38 -14
  148. package/src/ssr/LayoutFooter.tsx +1 -1
  149. package/src/ssr/LayoutHeader.tsx +17 -20
  150. package/src/ssr/LayoutHelp.tsx +1 -1
  151. package/src/ssr/LayoutRail.tsx +1 -2
  152. package/src/ssr/PageTrail.tsx +23 -0
  153. package/src/ssr/PageTrailRow.island.tsx +20 -0
  154. package/src/ssr/PageTrailRuntime.island.tsx +26 -0
  155. package/src/ssr/ProfilePreferences.island.tsx +7 -3
  156. package/src/ssr/RailApps.island.tsx +2 -2
  157. package/src/ssr/app-badges.ts +50 -6
  158. package/src/ssr/islands/index.ts +1 -0
  159. package/src/ssr/layout-runtime.ts +60 -0
  160. package/src/ssr/mobile-menu-history.ts +1 -1
  161. package/src/ssr/page-trail-runtime.ts +313 -0
  162. package/src/ssr/page-trail.browser-harness.tsx +265 -0
  163. package/src/ssr/page-trail.ts +59 -0
  164. package/src/ssr/platform-messages.ts +4 -0
  165. package/src/ssr/rail-navigation.ts +5 -3
  166. package/src/ssr/runtime.ts +6 -1
  167. package/src/styles/approvals.css +390 -0
  168. package/src/styles/effects.css +21 -4
  169. package/src/styles/global.css +39 -3
  170. package/src/styles/utilities-layout.css +24 -1
  171. package/src/styles/utilities-navigation.css +6 -18
@@ -125,7 +125,9 @@ export const buildAiMessageTimeline = (messages: AiStoredMessage[]): AiMessageTi
125
125
 
126
126
  const blocks = [
127
127
  ...steerBlocks,
128
- ...buildBlocksFromMessages(entries.map((stored) => ({ seq: stored.seq, message: stored.message, meta: stored.meta }))),
128
+ ...buildBlocksFromMessages(
129
+ entries.map((stored) => ({ seq: stored.seq, message: stored.message, meta: stored.meta, createdAt: stored.createdAt })),
130
+ ),
129
131
  ];
130
132
  items.push({
131
133
  type: "assistant",
@@ -1,4 +1,5 @@
1
1
  import { sql } from "bun";
2
+ import type { ApprovalDecisionChannel } from "../services/approvals";
2
3
 
3
4
  /**
4
5
  * AI-specific tool-call state only: approval, turn linkage, and the request id
@@ -8,7 +9,14 @@ import { sql } from "bun";
8
9
  */
9
10
  export type AiToolCallLocation = "server" | "client" | "client_view" | "client_interaction";
10
11
 
11
- export type AiToolApprovalState = "not_required" | "waiting" | "approved_once" | "approved_always" | "approved_by_preference" | "rejected";
12
+ export type AiToolApprovalState =
13
+ | "not_required"
14
+ | "waiting"
15
+ | "approved_once"
16
+ | "approved_for_chat"
17
+ | "approved_always"
18
+ | "approved_by_preference"
19
+ | "rejected";
12
20
 
13
21
  export const aiToolAudit = {
14
22
  /** Resolve the current server-recorded invocation, never a model-supplied chat id. */
@@ -140,12 +148,14 @@ export const aiToolAudit = {
140
148
  turnId: string;
141
149
  callId: string;
142
150
  approvalState: Exclude<AiToolApprovalState, "not_required" | "waiting">;
151
+ decidedVia?: ApprovalDecisionChannel;
143
152
  }): Promise<void> => {
144
153
  const approved = input.approvalState !== "rejected";
145
154
  await sql`
146
155
  UPDATE ai.tool_calls
147
156
  SET
148
157
  approval_state = ${input.approvalState},
158
+ approval_decided_via = ${input.decidedVia ?? null},
149
159
  status = CASE WHEN ${approved} THEN status ELSE 'rejected' END,
150
160
  approved_at = CASE WHEN ${approved} THEN now() ELSE approved_at END,
151
161
  rejected_at = CASE WHEN ${approved} THEN rejected_at ELSE now() END
package/src/ai/tools.ts CHANGED
@@ -4,6 +4,7 @@ import type { z } from "zod";
4
4
  import type { RequestActor } from "../server";
5
5
  import { aiToolNeedsApproval } from "./approvals";
6
6
  import type {
7
+ AiApprovalTarget,
7
8
  AiDataBoundary,
8
9
  AiFrontendToolMode,
9
10
  AiProjectFileToolSource,
@@ -13,6 +14,7 @@ import type {
13
14
  AiToolApprovalPolicy,
14
15
  AiToolDefinition,
15
16
  AiToolRuntime,
17
+ AiWebsiteReceipt,
16
18
  } from "./types";
17
19
 
18
20
  export const defineAiTool = <TInput extends z.ZodType, TOutput extends z.ZodType>(config: {
@@ -52,6 +54,7 @@ export const defineAiTool = <TInput extends z.ZodType, TOutput extends z.ZodType
52
54
  conversationId?: string;
53
55
  turnId?: string;
54
56
  attachedFilePaths?: ReadonlySet<string>;
57
+ localToolsEnabled?: boolean;
55
58
  allowedDataBoundaries?: AiDataBoundary[];
56
59
  projectFiles?: AiProjectFileToolSource;
57
60
  skillFiles?: AiSkillFileToolSource;
@@ -60,6 +63,12 @@ export const defineAiTool = <TInput extends z.ZodType, TOutput extends z.ZodType
60
63
  timeZone?: string;
61
64
  /** Localized short status; never include payloads or secrets. */
62
65
  reportProgress?: (message: string) => Promise<void>;
66
+ /** Asks like `requestApproval`, and lets the person remember the approval of `target`. */
67
+ requestApprovalFor?: (message: string, target: AiApprovalTarget) => Promise<boolean>;
68
+ /** Relays a decision for an effect another platform component consumes and executes. */
69
+ requestRelayedApproval?: (message: string, options: { approvalId?: string; target?: AiApprovalTarget }) => Promise<boolean>;
70
+ /** Shows requests a website approval let through as they go out, so the chat keeps them whatever the outcome. */
71
+ reportWebsiteReceipts?: (receipts: AiWebsiteReceipt[]) => Promise<void>;
63
72
  },
64
73
  ) => Promise<z.infer<TOutput>>,
65
74
  ): AiToolRuntime<TInput, TOutput> {
@@ -99,6 +108,7 @@ export type AiToolPreparationContext = {
99
108
  conversationId?: string;
100
109
  turnId?: string;
101
110
  attachedFilePaths?: ReadonlySet<string>;
111
+ localToolsEnabled?: boolean;
102
112
  allowedDataBoundaries?: AiDataBoundary[];
103
113
  projectFiles?: AiProjectFileToolSource;
104
114
  skillFiles?: AiSkillFileToolSource;
@@ -106,15 +116,93 @@ export type AiToolPreparationContext = {
106
116
  locale?: string;
107
117
  timeZone?: string;
108
118
  reportToolProgress?: (callId: string, message: string) => Promise<void>;
119
+ /** Uses the approved snapshot immediately before the tool executes its effect. */
120
+ consumeApproval?: (approvalCallId: string, input: unknown) => Promise<AiApprovalUse>;
121
+ /** Links a relayed decision to the record the effect owner will consume. */
122
+ linkApproval?: (approvalCallId: string, approvalId: string) => void;
123
+ /** Hears what a nested approval asks for before the approval is requested. */
124
+ describeApproval?: (approvalCallId: string, target: AiApprovalTarget) => void;
125
+ reportWebsiteReceipts?: (callId: string, receipts: AiWebsiteReceipt[]) => Promise<void>;
109
126
  };
110
127
 
128
+ /**
129
+ * What using an approval found: `approved` lets the effect run, `changed` means the approved snapshot no longer
130
+ * matches and asks again, and `used` means an earlier run of the call already used it, so the call must not run again.
131
+ */
132
+ export type AiApprovalUse = "approved" | "changed" | "used";
133
+
134
+ const approvalCallIdPattern = /^(.*)-approval-(\d+)$/;
135
+
136
+ /**
137
+ * Whether `askedCallIds` holds a question of the same tool call that Nessi asked after `approvalCallId`, the call's
138
+ * own approval or one of its numbered questions. A turn that continues after such a question runs the tool from its
139
+ * start again, so the approvals before it are used again by the same call.
140
+ */
141
+ export const aiApprovalAskedLater = (approvalCallId: string, askedCallIds: Iterable<string>): boolean => {
142
+ const own = approvalCallIdPattern.exec(approvalCallId);
143
+ const callId = own ? own[1] : approvalCallId,
144
+ index = own ? Number(own[2]) : -1;
145
+ for (const asked of askedCallIds) {
146
+ const later = approvalCallIdPattern.exec(asked);
147
+ if (later && later[1] === callId && Number(later[2]) > index) return true;
148
+ }
149
+ return false;
150
+ };
151
+
152
+ const approvalUsedError = () =>
153
+ new Error("This approval was already used, so Cloud does not run the call again. Check its outcome before you call again.");
154
+
111
155
  export const prepareAiTools = (input: AiToolPreparationContext & { tools?: AiRuntimeTool[] }): PreparedAiTools => {
112
156
  const canonicalNames = new Map<string, string>();
113
157
  const approvalPolicies = new Map<string, AiToolApprovalPolicy>();
114
158
  const frontendModes = new Map<string, AiFrontendToolMode>();
115
159
 
160
+ const approvalContext = async (toolInput: unknown, ctx: ToolContext, needsApproval: boolean) => {
161
+ const callId = ctx.callId;
162
+ const use = (approvalCallId: string): Promise<AiApprovalUse> =>
163
+ input.consumeApproval ? input.consumeApproval(approvalCallId, toolInput) : Promise.resolve("approved");
164
+ if (needsApproval && input.consumeApproval) {
165
+ const used = callId ? await use(callId) : "changed";
166
+ if (used === "used") throw approvalUsedError();
167
+ if (used === "changed") throw new Error("This approval no longer matches the call. Ask again.");
168
+ }
169
+ // Nessi numbers the approvals of one execution in request order; count them to name each one's call ID.
170
+ let approvals = 0;
171
+ const requestApproval = async (message: string, target?: AiApprovalTarget): Promise<boolean> => {
172
+ while (true) {
173
+ const approvalCallId = `${callId}-approval-${approvals++}`;
174
+ if (target) input.describeApproval?.(approvalCallId, target);
175
+ if (!(await ctx.requestApproval(message))) return false;
176
+ const used = await use(approvalCallId);
177
+ if (used === "approved") return true;
178
+ if (used === "used") throw approvalUsedError();
179
+ // A changed snapshot requires a new human decision under the next Nessi action ID.
180
+ }
181
+ };
182
+ return {
183
+ ...ctx,
184
+ requestApproval,
185
+ requestApprovalFor: (message: string, target: AiApprovalTarget) => requestApproval(message, target),
186
+ requestRelayedApproval: async (message: string, options: { approvalId?: string; target?: AiApprovalTarget }) => {
187
+ const approvalCallId = `${callId}-approval-${approvals++}`;
188
+ if (options.approvalId) input.linkApproval?.(approvalCallId, options.approvalId);
189
+ if (options.target) input.describeApproval?.(approvalCallId, options.target);
190
+ if (!(await ctx.requestApproval(message))) return false;
191
+ // The effect's own record is used where it runs. Without one, as for a website request from code, the relay
192
+ // that hands over the decision uses the chat's record; a decision it cannot use is not handed over as approval.
193
+ return options.approvalId !== undefined || (await use(approvalCallId)) === "approved";
194
+ },
195
+ };
196
+ };
197
+
116
198
  const tools = (input.tools ?? []).map((tool): Tool => {
117
- if (!isCloudAiTool(tool)) return tool;
199
+ if (!isCloudAiTool(tool)) {
200
+ if (tool.kind !== "server") return tool;
201
+ return {
202
+ ...tool,
203
+ execute: async (toolInput, ctx) => tool.execute(toolInput, await approvalContext(toolInput, ctx, tool.def.needsApproval === true)),
204
+ };
205
+ }
118
206
 
119
207
  canonicalNames.set(tool.def.name, tool.def.canonicalName ?? tool.def.name);
120
208
  approvalPolicies.set(tool.def.name, tool.def.approval);
@@ -130,16 +218,17 @@ export const prepareAiTools = (input: AiToolPreparationContext & { tools?: AiRun
130
218
  });
131
219
 
132
220
  if (tool.location === "server") {
133
- return nessiTool.server((toolInput, ctx) => {
221
+ return nessiTool.server(async (toolInput, ctx) => {
134
222
  if (!input.actor) throw new Error(`AI server tool "${tool.def.name}" requires a request actor.`);
135
223
  const report = input.reportToolProgress;
136
224
  const callId = ctx.callId;
137
225
  return tool.run(toolInput, {
138
- ...ctx,
226
+ ...(await approvalContext(toolInput, ctx, aiToolNeedsApproval(tool.def.approval))),
139
227
  actor: input.actor,
140
228
  conversationId: input.conversationId,
141
229
  turnId: input.turnId,
142
230
  attachedFilePaths: input.attachedFilePaths,
231
+ localToolsEnabled: input.localToolsEnabled,
143
232
  allowedDataBoundaries: input.allowedDataBoundaries,
144
233
  projectFiles: input.projectFiles,
145
234
  skillFiles: input.skillFiles,
@@ -147,6 +236,8 @@ export const prepareAiTools = (input: AiToolPreparationContext & { tools?: AiRun
147
236
  locale: input.locale,
148
237
  timeZone: input.timeZone,
149
238
  reportProgress: callId && report ? (message) => report(callId, message) : undefined,
239
+ reportWebsiteReceipts:
240
+ callId && input.reportWebsiteReceipts ? (receipts) => input.reportWebsiteReceipts!(callId, receipts) : undefined,
150
241
  });
151
242
  });
152
243
  }
package/src/ai/types.ts CHANGED
@@ -15,6 +15,7 @@ import type { z } from "zod";
15
15
  import type { CapabilitySentenceField } from "../_internal/capability-sentences";
16
16
  import type { CapabilityActionReview, CapabilityActionSentences, CloudResourceRef } from "../contracts/capabilities";
17
17
  import type { RequestActor } from "../server";
18
+ import type { AiLocalClient, AiLocalContext, AiLocalToolName } from "./local-tool-contracts";
18
19
  import type { AiTurnBlock } from "./protocol";
19
20
 
20
21
  export const AI_MODEL_CAPABILITIES = ["streaming", "tools", "vision", "transcription"] as const;
@@ -84,6 +85,7 @@ export type AiSettingsState =
84
85
  defaultModelId: string;
85
86
  visionModelId?: string;
86
87
  globalInstructions: string;
88
+ localClientInstructions: string;
87
89
  compactionInstructions: string;
88
90
  maxToolResultChars: number;
89
91
  firecrawlConfigured: boolean;
@@ -95,6 +97,7 @@ export type AiSettingsState =
95
97
  defaultModelId: string;
96
98
  visionModelId?: string;
97
99
  globalInstructions: string;
100
+ localClientInstructions: string;
98
101
  compactionInstructions: string;
99
102
  maxToolResultChars: number;
100
103
  firecrawlConfigured: boolean;
@@ -303,6 +306,8 @@ export type AiStoredMessage = {
303
306
  toolPresentations?: Record<string, AiToolPresentation>;
304
307
  /** How the person decided an approval; `expired` when the turn ended while it still waited. */
305
308
  toolOutcomes?: Record<string, "rejected" | "approved" | "expired">;
309
+ /** Requests a website approval let through, by tool call and then by `AiWebsiteReceipt.index`. */
310
+ websiteReceipts?: Record<string, Record<string, { method: string; url: string }>>;
306
311
  /** Why the turn failed, on the last message of its loop. The chat words it in the reader's language. */
307
312
  turnError?: AiTurnError;
308
313
  } | null;
@@ -420,6 +425,10 @@ export type AiPendingTurnAction =
420
425
  message?: string;
421
426
  review?: CapabilityActionReview;
422
427
  allowAlways: boolean;
428
+ /** Whether the approval can be remembered for this chat. */
429
+ allowChat: boolean;
430
+ /** The website origin a chat approval would allow, for an HTTP request a code run makes. */
431
+ website?: string;
423
432
  }
424
433
  | {
425
434
  type: "frontend_tool";
@@ -441,6 +450,7 @@ export type AiTurnAbortRequest =
441
450
  };
442
451
 
443
452
  export type AiPendingTurnActionRecord = {
453
+ approvalId?: string;
444
454
  turnId: string;
445
455
  conversationId: string;
446
456
  callId: string;
@@ -452,6 +462,10 @@ export type AiPendingTurnActionRecord = {
452
462
  review?: CapabilityActionReview;
453
463
  approvalScope: string;
454
464
  allowAlways: boolean;
465
+ /** Whether the approval can be remembered for this chat; defaults to `allowAlways`. */
466
+ allowChat?: boolean;
467
+ /** Remembered-approval name when it differs from `name`: the website or Action a code run asks for. */
468
+ rememberToolName?: string;
455
469
  frontendMode?: AiFrontendToolMode;
456
470
  resolvedEvent: InboundEvent | null;
457
471
  };
@@ -460,6 +474,7 @@ export type AiTurnToolSource = { kind: "none" } | { kind: "default"; appTools?:
460
474
 
461
475
  export type AiClientToolId =
462
476
  | "local_bash"
477
+ | AiLocalToolName
463
478
  | "code_run"
464
479
  | "code_action"
465
480
  | "code_inspect"
@@ -607,6 +622,11 @@ export type AiChatTurnRunConfig = {
607
622
  selectedSkillIds?: string[];
608
623
  /** Server-owned marker: model grants apply only to interactive Assistant chat. */
609
624
  assistantChat?: true;
625
+ /**
626
+ * Server-owned marker: a person started this turn in a signed-in browser session, not through `cld`, an API key,
627
+ * a delegated account, or a schedule. Only such a turn uses website approvals.
628
+ */
629
+ signedInSession?: true;
610
630
  kind?: "chat";
611
631
  input: Input;
612
632
  /** Stable public ID exposed as runtime context, not instructions. */
@@ -626,6 +646,8 @@ export type AiChatTurnRunConfig = {
626
646
  /** Whether this turn's materialized tools can inspect referenced images. */
627
647
  canInspectAttachedImages?: boolean;
628
648
  clientToolIds?: AiClientToolId[];
649
+ client?: AiLocalClient;
650
+ localContext?: AiLocalContext;
629
651
  toolSource?: AiTurnToolSource;
630
652
  toolApprovalContext?: {
631
653
  actorUserId: string;
@@ -971,6 +993,7 @@ export type AiConversationService = {
971
993
  savePendingTurnAction(input: AiPendingTurnActionRecord): Promise<void>;
972
994
  listPendingTurnActions(input: { conversationId: string; turnId: string }): Promise<AiPendingTurnAction[]>;
973
995
  getPendingTurnAction(input: { conversationId: string; turnId: string; callId: string }): Promise<AiPendingTurnActionRecord | null>;
996
+ findPendingTurnActionByApproval(input: { approvalId: string; ownerUserId: string }): Promise<AiPendingTurnActionRecord | null>;
974
997
  listPendingActionRecords(input: { conversationId: string; turnId: string }): Promise<AiPendingTurnActionRecord[]>;
975
998
  listResolvedPendingActions(input: { conversationId: string; turnId: string }): Promise<AiPendingTurnActionRecord[]>;
976
999
  resolvePendingTurnAction(input: {
@@ -979,6 +1002,7 @@ export type AiConversationService = {
979
1002
  callId: string;
980
1003
  event: InboundEvent;
981
1004
  }): Promise<AiPendingTurnActionRecord | null>;
1005
+ markPendingTurnActionConsumedInline(input: { conversationId: string; turnId: string; callId: string }): Promise<void>;
982
1006
  clearPendingTurnActions(input: { conversationId: string; turnId: string }): Promise<void>;
983
1007
  enqueueTurnSteer(input: {
984
1008
  conversationId: string;
@@ -1016,6 +1040,23 @@ export type AiAccessResult<TAccess = unknown> = {
1016
1040
 
1017
1041
  export type AiToolApprovalPolicy = "never" | "once" | "always" | { kind: "user-configurable"; default: "once" | "always"; scope?: string };
1018
1042
 
1043
+ /**
1044
+ * What a server tool asks a person to approve when the approval can be remembered: a website or an Action, not the
1045
+ * tool itself. A target can always be remembered for its chat.
1046
+ */
1047
+ export type AiApprovalTarget = {
1048
+ toolName: string;
1049
+ approvalScope: string;
1050
+ /** Whether the person may also remember it everywhere. */
1051
+ always: boolean;
1052
+ };
1053
+
1054
+ /**
1055
+ * A request a remembered website approval let through without asking. The chat shows it with its full URL and a revoke
1056
+ * action; `index` orders the receipts of one tool call, and a receipt reported again with the same index replaces it.
1057
+ */
1058
+ export type AiWebsiteReceipt = { index: number; method: string; url: string };
1059
+
1019
1060
  export type AiFrontendToolMode = "client" | "client_view" | "client_interaction";
1020
1061
 
1021
1062
  export type AiCapabilityToolPresentation = {
@@ -1063,6 +1104,7 @@ export type AiToolRuntime<TInput extends z.ZodType = z.ZodType, TOutput extends
1063
1104
  conversationId?: string;
1064
1105
  turnId?: string;
1065
1106
  attachedFilePaths?: ReadonlySet<string>;
1107
+ localToolsEnabled?: boolean;
1066
1108
  allowedDataBoundaries?: AiDataBoundary[];
1067
1109
  projectFiles?: AiProjectFileToolSource;
1068
1110
  skillFiles?: AiSkillFileToolSource;
@@ -1071,6 +1113,12 @@ export type AiToolRuntime<TInput extends z.ZodType = z.ZodType, TOutput extends
1071
1113
  timeZone?: string;
1072
1114
  /** Localized short status; never include payloads or secrets. */
1073
1115
  reportProgress?: (message: string) => Promise<void>;
1116
+ /** Asks like `requestApproval`, and lets the person remember the approval of `target`. */
1117
+ requestApprovalFor?: (message: string, target: AiApprovalTarget) => Promise<boolean>;
1118
+ /** Relays a decision for an effect another platform component consumes and executes. */
1119
+ requestRelayedApproval?: (message: string, options: { approvalId?: string; target?: AiApprovalTarget }) => Promise<boolean>;
1120
+ /** Shows requests a website approval let through as they go out, so the chat keeps them whatever the outcome. */
1121
+ reportWebsiteReceipts?: (receipts: AiWebsiteReceipt[]) => Promise<void>;
1074
1122
  },
1075
1123
  ): Promise<z.infer<TOutput>>;
1076
1124
  }