@agent-native/core 0.148.1 → 0.149.1
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/corpus/README.md +1 -1
- package/corpus/templates/analytics/changelog/2026-08-10-wide-tables-in-analytics-chat-now-scroll-horizontally-within.md +6 -0
- package/corpus/templates/calendar/app/components/calendar/DayView.tsx +13 -53
- package/corpus/templates/calendar/app/components/calendar/WeekView.tsx +11 -70
- package/corpus/templates/calendar/app/lib/event-layout.ts +122 -0
- package/corpus/templates/calendar/app/routes/_app.availability.tsx +1 -1
- package/corpus/templates/calendar/changelog/2026-08-10-opening-availability-settings-from-the-agent-lands-on-the-av.md +6 -0
- package/corpus/templates/calendar/vitest.config.ts +6 -1
- package/corpus/templates/clips/actions/lib/audio-only-transcription.ts +3 -1
- package/corpus/templates/clips/app/components/player/video-player.tsx +44 -4
- package/corpus/templates/clips/changelog/2026-08-10-fixed-transcription-failures-for-recordings-without-captured.md +6 -0
- package/corpus/templates/clips/changelog/2026-08-10-mobile-clips-can-now-enter-fullscreen-on-iphone-safari.md +6 -0
- package/corpus/templates/content/actions/_property-utils.ts +11 -1
- package/corpus/templates/content/actions/view-screen.ts +62 -12
- package/corpus/templates/content/server/plugins/agent-chat.ts +2 -0
- package/corpus/templates/design/.agents/skills/design-templates/SKILL.md +22 -0
- package/corpus/templates/design/AGENTS.md +3 -0
- package/corpus/templates/design/actions/create-design-from-template.ts +19 -0
- package/corpus/templates/design/actions/get-design-snapshot.ts +18 -0
- package/corpus/templates/design/actions/get-design-template.ts +178 -0
- package/corpus/templates/design/actions/list-designs.ts +3 -1
- package/corpus/templates/design/actions/view-screen.ts +31 -0
- package/corpus/templates/design/app/components/design/MultiScreenCanvas.tsx +225 -214
- package/corpus/templates/design/app/components/design/multi-screen/types.ts +3 -0
- package/corpus/templates/design/app/i18n-data.ts +78 -0
- package/corpus/templates/design/app/lib/design-authors.ts +64 -0
- package/corpus/templates/design/app/pages/Index.tsx +114 -4
- package/corpus/templates/design/app/pages/design-editor/generation-prompt-directives.ts +1 -0
- package/corpus/templates/design/changelog/2026-08-07-alt-dragging-an-element-to-copy-it-now-keeps-the-copy-visibl.md +6 -0
- package/corpus/templates/design/changelog/2026-08-07-design-cards-now-show-who-created-each-design-with-a-filter-.md +6 -0
- package/corpus/templates/design/changelog/2026-08-07-drawing-a-shape-on-the-overview-canvas-now-keeps-the-shape-s.md +6 -0
- package/corpus/templates/design/changelog/2026-08-07-hold-option-alt-while-dragging-a-frame-on-the-overview-canva.md +6 -0
- package/corpus/templates/design/server/lib/design-template-data.ts +143 -0
- package/corpus/templates/design/server/plugins/agent-chat.ts +1 -0
- package/corpus/templates/forms/AGENTS.md +2 -2
- package/corpus/templates/forms/actions/create-form.ts +1 -1
- package/corpus/templates/forms/actions/update-form.ts +5 -5
- package/corpus/templates/forms/changelog/2026-08-10-forms-now-keep-agent-applied-field-changes-visible-after-an-.md +6 -0
- package/corpus/templates/forms/changelog/2026-08-10-published-form-links-always-use-the-working-public-form-url.md +6 -0
- package/corpus/templates/forms/server/plugins/agent-chat.ts +2 -2
- package/corpus/templates/slides/AGENTS.md +4 -3
- package/corpus/templates/slides/actions/import-google-slides-reference.ts +56 -10
- package/corpus/templates/slides/actions/patch-deck.ts +18 -2
- package/corpus/templates/slides/app/components/deck/SlideRenderer.tsx +46 -0
- package/corpus/templates/slides/app/components/editor/BlockBubbleMenu.tsx +194 -3
- package/corpus/templates/slides/app/components/editor/EditorSidebar.tsx +22 -75
- package/corpus/templates/slides/app/components/editor/ExportMenu.tsx +5 -3
- package/corpus/templates/slides/app/components/editor/GeneratingSlidePreview.tsx +5 -1
- package/corpus/templates/slides/app/components/editor/GoogleDriveConnectionCta.tsx +15 -4
- package/corpus/templates/slides/app/components/editor/NewDeckReferenceStep.tsx +116 -55
- package/corpus/templates/slides/app/components/editor/SlideEditor.tsx +27 -4
- package/corpus/templates/slides/app/components/editor/slide-object-interactions.ts +45 -0
- package/corpus/templates/slides/app/components/onboarding/FirstDeckOnboardingFlow.tsx +143 -18
- package/corpus/templates/slides/app/i18n/en-US.ts +6 -0
- package/corpus/templates/slides/app/lib/export-pdf-client.ts +84 -25
- package/corpus/templates/slides/app/lib/generation-state.ts +7 -1
- package/corpus/templates/slides/app/lib/import-uploaded-deck.ts +11 -0
- package/corpus/templates/slides/app/pages/DeckEditor.tsx +44 -14
- package/corpus/templates/slides/app/pages/Index.tsx +124 -135
- package/corpus/templates/slides/changelog/2026-08-08-google-slides-export-now-tells-you-why-a-deck-fell-back-to-a.md +6 -0
- package/corpus/templates/slides/changelog/2026-08-09-delete-key-now-removes-a-selected-image.md +6 -0
- package/corpus/templates/slides/changelog/2026-08-09-editing-a-slide-in-an-imported-deck-no-longer-fails-to-save.md +6 -0
- package/corpus/templates/slides/changelog/2026-08-09-fixed-pdf-and-custom-size-powerpoint-imports-rendering-with-.md +6 -0
- package/corpus/templates/slides/changelog/2026-08-09-images-from-sites-without-cors-no-longer-export-blank.md +6 -0
- package/corpus/templates/slides/changelog/2026-08-09-imported-pdf-headings-no-longer-lose-the-space-between-color.md +6 -0
- package/corpus/templates/slides/changelog/2026-08-09-imported-slide-text-no-longer-renders-too-large-for-its-box.md +6 -0
- package/corpus/templates/slides/changelog/2026-08-09-large-decks-no-longer-render-slowly-or-glitch-in-the-editor.md +6 -0
- package/corpus/templates/slides/changelog/2026-08-09-revise-highlighted-slide-text-with-ai.md +6 -0
- package/corpus/templates/slides/changelog/2026-08-09-the-deck-editor-no-longer-stays-dimmed-after-the-window-narro.md +6 -0
- package/corpus/templates/slides/changelog/2026-08-10-imported-reference-decks-now-show-clear-success-and-stay-sel.md +6 -0
- package/corpus/templates/slides/changelog/2026-08-10-pasted-google-slides-links-import-after-reconnecting-google-.md +6 -0
- package/corpus/templates/slides/server/handlers/google-docs-auth.ts +4 -0
- package/corpus/templates/slides/server/handlers/import/html-converter.ts +115 -22
- package/corpus/templates/slides/server/handlers/import/pdf-fidelity-parser.ts +11 -2
- package/corpus/templates/slides/server/lib/fetch-remote-image.ts +192 -0
- package/corpus/templates/slides/server/lib/google-docs-oauth.ts +13 -0
- package/corpus/templates/slides/server/lib/image-proxy-url.ts +192 -0
- package/corpus/templates/slides/server/plugins/agent-chat.ts +1 -1
- package/corpus/templates/slides/server/routes/api/image-proxy.get.ts +65 -0
- package/dist/agent/engine/registry.d.ts +8 -2
- package/dist/agent/engine/registry.js +63 -29
- package/dist/agent/production-agent.js +7 -0
- package/dist/agent/run-manager.d.ts +5 -4
- package/dist/agent/run-manager.js +6 -5
- package/dist/cli/claude-code-participant.d.ts +2 -1
- package/dist/cli/claude-code-participant.js +19 -0
- package/dist/cli/code-agent-executor.js +223 -0
- package/dist/cli/create-workspace.js +3 -3
- package/dist/cli/create.d.ts +3 -2
- package/dist/cli/create.js +20 -24
- package/dist/client/AgentPanel.js +8 -4
- package/dist/client/AssistantChat.js +34 -1
- package/dist/client/agent-chat-adapter.d.ts +1 -0
- package/dist/client/agent-chat-adapter.js +10 -2
- package/dist/client/chat/markdown-renderer.d.ts +1 -0
- package/dist/client/chat/markdown-renderer.js +4 -0
- package/dist/client/chat/message-components.d.ts +1 -0
- package/dist/client/chat/message-components.js +9 -1
- package/dist/client/chat/tool-call-display.d.ts +8 -0
- package/dist/client/chat/tool-call-display.js +30 -21
- package/dist/client/chat-first/app-pane.js +1 -1
- package/dist/client/chat-first/apps-rail.d.ts +1 -1
- package/dist/client/chat-first/apps-rail.js +11 -11
- package/dist/client/chat-first/chat-history.d.ts +1 -1
- package/dist/client/chat-first/chat-history.js +2 -0
- package/dist/client/chat-first/primary-nav.js +1 -1
- package/dist/client/progress/RunsTray.js +14 -1
- package/dist/client/resources/McpConnectionSuggestion.js +2 -2
- package/dist/client/settings/SettingsTabsPage.d.ts +3 -1
- package/dist/client/settings/SettingsTabsPage.js +2 -2
- package/dist/client/sse-event-processor.d.ts +8 -0
- package/dist/client/sse-event-processor.js +13 -6
- package/dist/collab/awareness.d.ts +2 -2
- package/dist/collab/routes.d.ts +1 -1
- package/dist/deploy/build.d.ts +5 -7
- package/dist/deploy/build.js +19 -12
- package/dist/localization/default-messages.js +1 -1
- package/dist/notifications/routes.d.ts +3 -3
- package/dist/observability/routes.d.ts +6 -6
- package/dist/provider-api/actions/provider-api.d.ts +2 -2
- package/dist/scripts/call-agent.js +3 -3
- package/dist/scripts/describe-workspace-apps.js +1 -1
- package/dist/server/agent-capabilities.js +0 -0
- package/dist/server/agent-chat/context-tools.js +1 -1
- package/dist/server/agent-chat/prompt-resources.js +1 -1
- package/dist/server/agent-chat-plugin.js +1 -1
- package/dist/server/auth.js +190 -42
- package/dist/server/better-auth-migrations.js +53 -0
- package/dist/server/http-response-telemetry.js +7 -1
- package/dist/server/onboarding-html.js +119 -33
- package/dist/server/transcribe-voice.d.ts +1 -1
- package/dist/shared/password-policy.d.ts +2 -1
- package/dist/shared/password-policy.js +2 -1
- package/dist/styles/agent-conversation.css +20 -3
- package/dist/styles/agent-native.css +14 -2
- package/dist/templates/default/app/i18n/ar-SA.ts +1 -1
- package/dist/templates/default/app/i18n/de-DE.ts +1 -1
- package/dist/templates/default/app/i18n/en-US.ts +1 -1
- package/dist/templates/default/app/i18n/es-ES.ts +1 -1
- package/dist/templates/default/app/i18n/fr-FR.ts +1 -1
- package/dist/templates/default/app/i18n/hi-IN.ts +1 -1
- package/dist/templates/default/app/i18n/ja-JP.ts +1 -1
- package/dist/templates/default/app/i18n/ko-KR.ts +1 -1
- package/dist/templates/default/app/i18n/pt-BR.ts +1 -1
- package/dist/templates/default/app/i18n/zh-CN.ts +1 -1
- package/dist/templates/default/app/i18n/zh-TW.ts +1 -1
- package/dist/templates/default/pnpm-workspace.yaml +1 -0
- package/dist/templates/workspace-root/pnpm-workspace.yaml +2 -0
- package/docs/content/cloneable-saas.mdx +6 -6
- package/docs/content/deployment.mdx +10 -8
- package/docs/content/environment-variables.mdx +4 -3
- package/package.json +2 -2
- package/src/templates/default/app/i18n/ar-SA.ts +1 -1
- package/src/templates/default/app/i18n/de-DE.ts +1 -1
- package/src/templates/default/app/i18n/en-US.ts +1 -1
- package/src/templates/default/app/i18n/es-ES.ts +1 -1
- package/src/templates/default/app/i18n/fr-FR.ts +1 -1
- package/src/templates/default/app/i18n/hi-IN.ts +1 -1
- package/src/templates/default/app/i18n/ja-JP.ts +1 -1
- package/src/templates/default/app/i18n/ko-KR.ts +1 -1
- package/src/templates/default/app/i18n/pt-BR.ts +1 -1
- package/src/templates/default/app/i18n/zh-CN.ts +1 -1
- package/src/templates/default/app/i18n/zh-TW.ts +1 -1
- package/src/templates/default/pnpm-workspace.yaml +1 -0
- package/src/templates/workspace-root/pnpm-workspace.yaml +2 -0
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
import net from "node:net";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Hostnames that never belong to a legitimate slide image but are classic
|
|
5
|
+
* SSRF targets. Cloud metadata endpoints resolve to a link-local address
|
|
6
|
+
* that `isPrivateAddress` already rejects; they are listed here so the
|
|
7
|
+
* request is refused before a DNS query is issued.
|
|
8
|
+
*/
|
|
9
|
+
const BLOCKED_HOSTNAMES = new Set([
|
|
10
|
+
"localhost",
|
|
11
|
+
"metadata.google.internal",
|
|
12
|
+
"metadata",
|
|
13
|
+
]);
|
|
14
|
+
|
|
15
|
+
const BLOCKED_SUFFIXES = [".localhost", ".local", ".internal", ".home.arpa"];
|
|
16
|
+
|
|
17
|
+
/** Largest image we are willing to buffer and hand back to the browser. */
|
|
18
|
+
export const MAX_PROXIED_IMAGE_BYTES = 15 * 1024 * 1024;
|
|
19
|
+
|
|
20
|
+
/** Redirect hops to follow. Each hop is re-validated before it is fetched. */
|
|
21
|
+
export const MAX_PROXY_REDIRECTS = 3;
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Whether an IP literal points somewhere inside our own infrastructure.
|
|
25
|
+
* Anything unroutable, loopback, link-local, or RFC1918 is refused, as is
|
|
26
|
+
* any address we cannot parse — unknown means unsafe here.
|
|
27
|
+
*/
|
|
28
|
+
export function isPrivateAddress(address: string): boolean {
|
|
29
|
+
const version = net.isIP(address);
|
|
30
|
+
|
|
31
|
+
if (version === 4) {
|
|
32
|
+
const parts = address.split(".").map((part) => Number(part));
|
|
33
|
+
if (parts.length !== 4 || parts.some((part) => Number.isNaN(part))) {
|
|
34
|
+
return true;
|
|
35
|
+
}
|
|
36
|
+
const [a, b] = parts;
|
|
37
|
+
if (a === 0 || a === 10 || a === 127) return true;
|
|
38
|
+
if (a === 169 && b === 254) return true;
|
|
39
|
+
if (a === 172 && b >= 16 && b <= 31) return true;
|
|
40
|
+
if (a === 192 && b === 168) return true;
|
|
41
|
+
if (a === 100 && b >= 64 && b <= 127) return true;
|
|
42
|
+
if (a >= 224) return true;
|
|
43
|
+
return false;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
if (version === 6) {
|
|
47
|
+
// Classify on the expanded hextets, never on the text. `::ffff:127.0.0.1`
|
|
48
|
+
// and `::ffff:7f00:1` are the same address, and a prefix-matching check
|
|
49
|
+
// sees only the first.
|
|
50
|
+
const hextets = expandIpv6(address);
|
|
51
|
+
if (!hextets) return true;
|
|
52
|
+
|
|
53
|
+
const embedsIpv4 =
|
|
54
|
+
hextets.slice(0, 5).every((part) => part === 0) &&
|
|
55
|
+
(hextets[5] === 0xffff || hextets[5] === 0);
|
|
56
|
+
if (embedsIpv4 && (hextets[6] !== 0 || hextets[7] !== 0)) {
|
|
57
|
+
const ipv4 = [
|
|
58
|
+
hextets[6] >> 8,
|
|
59
|
+
hextets[6] & 0xff,
|
|
60
|
+
hextets[7] >> 8,
|
|
61
|
+
hextets[7] & 0xff,
|
|
62
|
+
].join(".");
|
|
63
|
+
return isPrivateAddress(ipv4);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// Unspecified (::) and loopback (::1).
|
|
67
|
+
if (hextets.every((part) => part === 0)) return true;
|
|
68
|
+
if (hextets.slice(0, 7).every((part) => part === 0) && hextets[7] === 1) {
|
|
69
|
+
return true;
|
|
70
|
+
}
|
|
71
|
+
// Unique-local fc00::/7, link-local fe80::/10, multicast ff00::/8.
|
|
72
|
+
if ((hextets[0] & 0xfe00) === 0xfc00) return true;
|
|
73
|
+
if ((hextets[0] & 0xffc0) === 0xfe80) return true;
|
|
74
|
+
if ((hextets[0] & 0xff00) === 0xff00) return true;
|
|
75
|
+
// NAT64 well-known prefix 64:ff9b::/96 tunnels an IPv4 destination.
|
|
76
|
+
if (hextets[0] === 0x0064 && hextets[1] === 0xff9b) {
|
|
77
|
+
const ipv4 = [
|
|
78
|
+
hextets[6] >> 8,
|
|
79
|
+
hextets[6] & 0xff,
|
|
80
|
+
hextets[7] >> 8,
|
|
81
|
+
hextets[7] & 0xff,
|
|
82
|
+
].join(".");
|
|
83
|
+
return isPrivateAddress(ipv4);
|
|
84
|
+
}
|
|
85
|
+
return false;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
return true;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Expand any textual IPv6 form into its 8 hextets, including `::` elision and
|
|
93
|
+
* a dotted IPv4 tail. Returns null when the input is not parseable.
|
|
94
|
+
*/
|
|
95
|
+
export function expandIpv6(address: string): number[] | null {
|
|
96
|
+
// Zone indices ("fe80::1%eth0") are routing hints, not part of the address.
|
|
97
|
+
const bare = address.toLowerCase().split("%")[0];
|
|
98
|
+
if (!bare) return null;
|
|
99
|
+
|
|
100
|
+
let head = bare;
|
|
101
|
+
let tail = "";
|
|
102
|
+
const elision = bare.indexOf("::");
|
|
103
|
+
if (elision !== -1) {
|
|
104
|
+
head = bare.slice(0, elision);
|
|
105
|
+
tail = bare.slice(elision + 2);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
const parseGroups = (segment: string): string[] =>
|
|
109
|
+
segment.length === 0 ? [] : segment.split(":");
|
|
110
|
+
|
|
111
|
+
const headGroups = parseGroups(head);
|
|
112
|
+
const tailGroups = parseGroups(tail);
|
|
113
|
+
const all = [...headGroups, ...tailGroups];
|
|
114
|
+
|
|
115
|
+
// A dotted IPv4 tail occupies the final two hextets.
|
|
116
|
+
const last = all[all.length - 1];
|
|
117
|
+
let ipv4Tail: number[] | null = null;
|
|
118
|
+
if (last?.includes(".")) {
|
|
119
|
+
if (net.isIP(last) !== 4) return null;
|
|
120
|
+
const octets = last.split(".").map((part) => Number(part));
|
|
121
|
+
if (
|
|
122
|
+
octets.some((part) => !Number.isInteger(part) || part < 0 || part > 255)
|
|
123
|
+
)
|
|
124
|
+
return null;
|
|
125
|
+
ipv4Tail = [(octets[0] << 8) | octets[1], (octets[2] << 8) | octets[3]];
|
|
126
|
+
if (tailGroups.length > 0) tailGroups.pop();
|
|
127
|
+
else headGroups.pop();
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
const toHextets = (groups: string[]): number[] | null => {
|
|
131
|
+
const out: number[] = [];
|
|
132
|
+
for (const group of groups) {
|
|
133
|
+
if (!/^[0-9a-f]{1,4}$/.test(group)) return null;
|
|
134
|
+
out.push(parseInt(group, 16));
|
|
135
|
+
}
|
|
136
|
+
return out;
|
|
137
|
+
};
|
|
138
|
+
|
|
139
|
+
const headParts = toHextets(headGroups);
|
|
140
|
+
const tailParts = toHextets(tailGroups);
|
|
141
|
+
if (!headParts || !tailParts) return null;
|
|
142
|
+
|
|
143
|
+
const explicit = [...headParts, ...tailParts, ...(ipv4Tail ? ipv4Tail : [])]
|
|
144
|
+
.length;
|
|
145
|
+
if (explicit > 8) return null;
|
|
146
|
+
|
|
147
|
+
if (elision === -1) {
|
|
148
|
+
if (explicit !== 8) return null;
|
|
149
|
+
return [...headParts, ...(ipv4Tail ?? [])];
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
const fill = new Array(8 - explicit).fill(0) as number[];
|
|
153
|
+
return [...headParts, ...fill, ...tailParts, ...(ipv4Tail ?? [])];
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* Parse a caller-supplied image URL, refusing anything that is not a plain
|
|
158
|
+
* public http(s) resource. Returns null rather than throwing so callers can
|
|
159
|
+
* answer with a single 400.
|
|
160
|
+
*/
|
|
161
|
+
export function parseProxyableImageUrl(raw: string): URL | null {
|
|
162
|
+
if (!raw) return null;
|
|
163
|
+
|
|
164
|
+
let url: URL;
|
|
165
|
+
try {
|
|
166
|
+
url = new URL(raw);
|
|
167
|
+
} catch {
|
|
168
|
+
// coercion-ok: null is this function's documented "refused" result and
|
|
169
|
+
// the caller answers 400; an unparseable URL carries no other detail.
|
|
170
|
+
return null;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
if (url.protocol !== "http:" && url.protocol !== "https:") return null;
|
|
174
|
+
// Credentials in the URL would be replayed by the server on the user's
|
|
175
|
+
// behalf against a host they may not control.
|
|
176
|
+
if (url.username || url.password) return null;
|
|
177
|
+
|
|
178
|
+
const host = url.hostname.toLowerCase().replace(/\.$/, "");
|
|
179
|
+
if (!host) return null;
|
|
180
|
+
if (BLOCKED_HOSTNAMES.has(host)) return null;
|
|
181
|
+
if (BLOCKED_SUFFIXES.some((suffix) => host.endsWith(suffix))) return null;
|
|
182
|
+
|
|
183
|
+
const literal = host.startsWith("[") ? host.slice(1, -1) : host;
|
|
184
|
+
if (net.isIP(literal) && isPrivateAddress(literal)) return null;
|
|
185
|
+
|
|
186
|
+
return url;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
// A hostname is no longer validated here before fetching. Resolving once for
|
|
190
|
+
// the check and again for the connection let a DNS-rebinding host answer
|
|
191
|
+
// differently each time, so the address check now lives in the socket's
|
|
192
|
+
// `lookup` — see `publicOnlyLookup` in fetch-remote-image.ts.
|
|
@@ -66,7 +66,7 @@ Image workflow is strict. For direct insertion, call generate-image-api with ins
|
|
|
66
66
|
|
|
67
67
|
Provider-specific Slides actions are shortcuts, not limits. If a first-class action cannot express the exact Google Drive endpoint, file metadata field, export format, query, request body, pagination mode, payload shape, or API version needed, call provider-api-catalog and provider-api-docs as needed, then call provider-api-request against the real provider API. Use the raw provider API escape hatch instead of weakening the answer or claiming Slides cannot do something the underlying Google Drive API can do.
|
|
68
68
|
|
|
69
|
-
Slides' Google Drive provider API uses the user's connected Google Docs OAuth account.
|
|
69
|
+
Slides' Google Drive provider API uses the user's connected Google Docs OAuth account. Picker imports keep the per-file drive.file path, while pasted Google Slides links use Drive export access and may ask the user to reconnect Google. For large Drive file lists or metadata sweeps, pass stageAs and pagination options to provider-api-request, then use query-staged-dataset to count, filter, group, or project the staged rows.
|
|
70
70
|
|
|
71
71
|
When a Google Drive or Google Slides request needs authentication, tell the user to use the Connect Google button shown in Slides. Do not expose internal routes, API endpoints, OAuth setup instructions, client IDs, or keys in the response, and do not ask the user to configure an API manually.`,
|
|
72
72
|
mentionProviders: async () => {
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { getSession } from "@agent-native/core/server";
|
|
2
|
+
import { defineEventHandler, getQuery, setResponseStatus } from "h3";
|
|
3
|
+
|
|
4
|
+
import {
|
|
5
|
+
fetchRemoteImage,
|
|
6
|
+
type RemoteImageFailure,
|
|
7
|
+
} from "../../lib/fetch-remote-image.js";
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Re-serve a remote image from our own origin.
|
|
11
|
+
*
|
|
12
|
+
* PDF/PPTX export rasterizes the slide DOM through a canvas, and the browser
|
|
13
|
+
* blanks out any image whose host does not send `Access-Control-Allow-Origin`.
|
|
14
|
+
* No client-side flag can override that, so images on hosts without CORS have
|
|
15
|
+
* to come back through us to be same-origin.
|
|
16
|
+
*
|
|
17
|
+
* This is an authenticated, image-only, size-capped fetcher — not a general
|
|
18
|
+
* proxy. See `fetch-remote-image.ts` for the address pinning that keeps it
|
|
19
|
+
* from being turned into an SSRF primitive.
|
|
20
|
+
*/
|
|
21
|
+
const FAILURE_STATUS: Record<RemoteImageFailure, number> = {
|
|
22
|
+
"unsupported-url": 400,
|
|
23
|
+
"blocked-address": 400,
|
|
24
|
+
"fetch-failed": 502,
|
|
25
|
+
"too-many-redirects": 502,
|
|
26
|
+
"not-an-image": 415,
|
|
27
|
+
"too-large": 413,
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
const FAILURE_MESSAGE: Record<RemoteImageFailure, string> = {
|
|
31
|
+
"unsupported-url": "Unsupported image URL",
|
|
32
|
+
"blocked-address": "Unsupported image URL",
|
|
33
|
+
"fetch-failed": "Could not fetch image",
|
|
34
|
+
"too-many-redirects": "Too many redirects",
|
|
35
|
+
"not-an-image": "Not an image",
|
|
36
|
+
"too-large": "Image too large",
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
export default defineEventHandler(async (event) => {
|
|
40
|
+
const session = await getSession(event);
|
|
41
|
+
if (!session?.email) {
|
|
42
|
+
setResponseStatus(event, 401);
|
|
43
|
+
return { error: "Unauthorized" };
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const raw = getQuery(event).url;
|
|
47
|
+
if (typeof raw !== "string") {
|
|
48
|
+
setResponseStatus(event, 400);
|
|
49
|
+
return { error: "Missing url" };
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const result = await fetchRemoteImage(raw);
|
|
53
|
+
if (!result.ok) {
|
|
54
|
+
setResponseStatus(event, FAILURE_STATUS[result.reason]);
|
|
55
|
+
return { error: FAILURE_MESSAGE[result.reason] };
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
event.node?.res?.setHeader("Content-Type", result.contentType);
|
|
59
|
+
event.node?.res?.setHeader("Content-Length", String(result.body.byteLength));
|
|
60
|
+
event.node?.res?.setHeader("Cache-Control", "private, max-age=3600");
|
|
61
|
+
// The canvas reads these pixels back, so the response must be explicitly
|
|
62
|
+
// usable cross-origin even though it is served from our own host.
|
|
63
|
+
event.node?.res?.setHeader("Access-Control-Allow-Origin", "*");
|
|
64
|
+
return result.body;
|
|
65
|
+
});
|
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
*
|
|
8
8
|
* Built-in engines (anthropic, ai-sdk) are auto-registered by builtin.ts.
|
|
9
9
|
*/
|
|
10
|
+
import { type BuilderCredentialLookupIdentity } from "../../server/credential-provider.js";
|
|
10
11
|
import type { AgentEngine, EngineCapabilities } from "./types.js";
|
|
11
12
|
export interface AgentEngineEntry {
|
|
12
13
|
/** Unique name, e.g. "anthropic", "ai-sdk:anthropic", "ai-sdk:openai" */
|
|
@@ -131,7 +132,7 @@ export declare function detectEngineFromEnvForRequest(): Promise<AgentEngineEntr
|
|
|
131
132
|
* `/builder/status` resolves them via the same request org context, and the
|
|
132
133
|
* chat engine picker must not disagree with that card.
|
|
133
134
|
*/
|
|
134
|
-
export declare function detectEngineFromUserSecrets(): Promise<AgentEngineEntry | null>;
|
|
135
|
+
export declare function detectEngineFromUserSecrets(identity?: BuilderCredentialLookupIdentity): Promise<AgentEngineEntry | null>;
|
|
135
136
|
/**
|
|
136
137
|
* Legacy inline API keys on the global `agent-engine` settings row are
|
|
137
138
|
* intentionally ignored. That row is deployment-wide, so treating
|
|
@@ -158,7 +159,9 @@ export declare function isStoredEngineUsable(stored: unknown, entry: AgentEngine
|
|
|
158
159
|
* deploy env vars; this async helper is what request-time routes should use
|
|
159
160
|
* when deciding whether a stored engine can actually run for the current user.
|
|
160
161
|
*/
|
|
161
|
-
export declare function isStoredEngineUsableForRequest(stored: unknown, entry: AgentEngineEntry
|
|
162
|
+
export declare function isStoredEngineUsableForRequest(stored: unknown, entry: AgentEngineEntry, options?: {
|
|
163
|
+
credentialIdentity?: BuilderCredentialLookupIdentity;
|
|
164
|
+
}): Promise<boolean>;
|
|
162
165
|
/**
|
|
163
166
|
* Request-aware credential preflight for an already-resolved engine instance.
|
|
164
167
|
* `resolveEngine()` may still return a default or explicitly requested engine
|
|
@@ -167,6 +170,7 @@ export declare function isStoredEngineUsableForRequest(stored: unknown, entry: A
|
|
|
167
170
|
*/
|
|
168
171
|
export declare function isResolvedEngineUsableForRequest(engine: AgentEngine, options?: {
|
|
169
172
|
apiKey?: string;
|
|
173
|
+
credentialIdentity?: BuilderCredentialLookupIdentity;
|
|
170
174
|
}): Promise<boolean>;
|
|
171
175
|
export interface ResolveEngineConfig {
|
|
172
176
|
/** Explicit engine name or instance from createAgentChatPlugin options */
|
|
@@ -186,6 +190,8 @@ export interface ResolveEngineConfig {
|
|
|
186
190
|
model?: string;
|
|
187
191
|
/** App/template id used for org-scoped per-app model defaults. */
|
|
188
192
|
appId?: string;
|
|
193
|
+
/** Verified owner identity for request-scoped Builder credential capture. */
|
|
194
|
+
credentialIdentity?: BuilderCredentialLookupIdentity;
|
|
189
195
|
}
|
|
190
196
|
/**
|
|
191
197
|
* Engine name a caller explicitly selected, when {@link resolveEngine} will
|
|
@@ -401,20 +401,26 @@ function shouldTraceEngineDetection() {
|
|
|
401
401
|
* `/builder/status` resolves them via the same request org context, and the
|
|
402
402
|
* chat engine picker must not disagree with that card.
|
|
403
403
|
*/
|
|
404
|
-
export async function detectEngineFromUserSecrets() {
|
|
404
|
+
export async function detectEngineFromUserSecrets(identity) {
|
|
405
405
|
const traceLookup = shouldTraceEngineDetection();
|
|
406
|
-
let email;
|
|
407
|
-
let orgId;
|
|
406
|
+
let email = identity?.userEmail?.trim() || undefined;
|
|
407
|
+
let orgId = identity?.orgId;
|
|
408
408
|
try {
|
|
409
409
|
const { getRequestUserEmail, getRequestOrgId } = await import("../../server/request-context.js");
|
|
410
|
-
email
|
|
411
|
-
orgId
|
|
410
|
+
email ??= getRequestUserEmail();
|
|
411
|
+
if (orgId === undefined)
|
|
412
|
+
orgId = getRequestOrgId();
|
|
412
413
|
}
|
|
413
414
|
catch {
|
|
415
|
+
if (!email) {
|
|
416
|
+
if (traceLookup) {
|
|
417
|
+
console.log(`[engine-detect] result=null reason=no-request-context email=(unknown) orgId=(unknown)`);
|
|
418
|
+
}
|
|
419
|
+
return null;
|
|
420
|
+
}
|
|
414
421
|
if (traceLookup) {
|
|
415
|
-
console.log(`[engine-detect]
|
|
422
|
+
console.log(`[engine-detect] request context unavailable; using explicit identity email=${email} orgId=${orgId ?? "(none)"}`);
|
|
416
423
|
}
|
|
417
|
-
return null;
|
|
418
424
|
}
|
|
419
425
|
if (!email) {
|
|
420
426
|
if (traceLookup) {
|
|
@@ -427,8 +433,9 @@ export async function detectEngineFromUserSecrets() {
|
|
|
427
433
|
return false;
|
|
428
434
|
if (entry.requiredEnvVars.length === 0)
|
|
429
435
|
return false;
|
|
430
|
-
if (entry.name === "builder")
|
|
431
|
-
return hasUsableBuilderConnection();
|
|
436
|
+
if (entry.name === "builder") {
|
|
437
|
+
return hasUsableBuilderConnection(identity);
|
|
438
|
+
}
|
|
432
439
|
for (const key of entry.requiredEnvVars) {
|
|
433
440
|
// A throw here means the credential store could not be read. Let it
|
|
434
441
|
// propagate: swallowing it reports "no provider connected" to a user
|
|
@@ -520,8 +527,8 @@ async function resolveProviderBaseUrl(envVar) {
|
|
|
520
527
|
* keeps that distinction instead of reporting "connect a provider" to a user
|
|
521
528
|
* whose org-shared keys exist but were unreadable.
|
|
522
529
|
*/
|
|
523
|
-
async function hasUsableBuilderConnection() {
|
|
524
|
-
const creds = await resolveBuilderCredentialsDetailed();
|
|
530
|
+
async function hasUsableBuilderConnection(identity) {
|
|
531
|
+
const creds = await resolveBuilderCredentialsDetailed(identity);
|
|
525
532
|
assertCredentialStoreReadable(creds);
|
|
526
533
|
return Boolean(creds.privateKey && creds.publicKey);
|
|
527
534
|
}
|
|
@@ -567,7 +574,7 @@ async function apiKeyBelongsToDifferentProvider(apiKey, selectedEntry) {
|
|
|
567
574
|
}
|
|
568
575
|
return false;
|
|
569
576
|
}
|
|
570
|
-
async function engineCreateConfigForEntry(entry, apiKey, extra, preferResolvedCredential = false, apiKeyEnvVar) {
|
|
577
|
+
async function engineCreateConfigForEntry(entry, apiKey, extra, preferResolvedCredential = false, apiKeyEnvVar, credentialIdentity) {
|
|
571
578
|
const safeExtra = { ...(extra ?? {}) };
|
|
572
579
|
let matchingApiKey = apiKey;
|
|
573
580
|
// A declared provenance settles the question without inspecting values: a
|
|
@@ -632,6 +639,25 @@ async function engineCreateConfigForEntry(entry, apiKey, extra, preferResolvedCr
|
|
|
632
639
|
safeExtra.baseUrl = baseUrl;
|
|
633
640
|
}
|
|
634
641
|
}
|
|
642
|
+
if (entry.name === "builder" &&
|
|
643
|
+
(credentialIdentity !== undefined || safeExtra.credentials == null)) {
|
|
644
|
+
// Builder authentication is a private/public key pair, not the single
|
|
645
|
+
// provider key carried by ResolveEngineConfig. Capture the scoped pair
|
|
646
|
+
// while the verified request identity is available so a later stream or
|
|
647
|
+
// detached run cannot resolve credentials from the wrong ambient context.
|
|
648
|
+
const creds = await resolveBuilderCredentialsDetailed(credentialIdentity);
|
|
649
|
+
assertCredentialStoreReadable(creds);
|
|
650
|
+
if (credentialIdentity !== undefined ||
|
|
651
|
+
creds.source !== null ||
|
|
652
|
+
creds.lookupFailed) {
|
|
653
|
+
safeExtra.credentials = {
|
|
654
|
+
privateKey: creds.privateKey,
|
|
655
|
+
publicKey: creds.publicKey,
|
|
656
|
+
userId: creds.userId,
|
|
657
|
+
orgName: creds.orgName,
|
|
658
|
+
};
|
|
659
|
+
}
|
|
660
|
+
}
|
|
635
661
|
return engineCreateConfig(entry, matchingApiKey, safeExtra);
|
|
636
662
|
}
|
|
637
663
|
/**
|
|
@@ -661,15 +687,16 @@ export function isStoredEngineUsable(stored, entry) {
|
|
|
661
687
|
* deploy env vars; this async helper is what request-time routes should use
|
|
662
688
|
* when deciding whether a stored engine can actually run for the current user.
|
|
663
689
|
*/
|
|
664
|
-
export async function isStoredEngineUsableForRequest(stored, entry) {
|
|
690
|
+
export async function isStoredEngineUsableForRequest(stored, entry, options = {}) {
|
|
665
691
|
if (!isAgentEnginePackageInstalled(entry))
|
|
666
692
|
return false;
|
|
667
693
|
if (isAgentEngineSettingConfigured(stored))
|
|
668
694
|
return true;
|
|
669
695
|
if (entry.requiredEnvVars.length === 0)
|
|
670
696
|
return true;
|
|
671
|
-
if (entry.name === "builder")
|
|
672
|
-
return hasUsableBuilderConnection();
|
|
697
|
+
if (entry.name === "builder") {
|
|
698
|
+
return hasUsableBuilderConnection(options.credentialIdentity);
|
|
699
|
+
}
|
|
673
700
|
for (const key of entry.requiredEnvVars) {
|
|
674
701
|
if (!(await resolveUsableProviderSecret(key)))
|
|
675
702
|
return false;
|
|
@@ -692,8 +719,9 @@ export async function isResolvedEngineUsableForRequest(engine, options = {}) {
|
|
|
692
719
|
return false;
|
|
693
720
|
if (entry.requiredEnvVars.length === 0)
|
|
694
721
|
return true;
|
|
695
|
-
if (entry.name === "builder")
|
|
696
|
-
return hasUsableBuilderConnection();
|
|
722
|
+
if (entry.name === "builder") {
|
|
723
|
+
return hasUsableBuilderConnection(options.credentialIdentity);
|
|
724
|
+
}
|
|
697
725
|
if (options.apiKey?.trim()) {
|
|
698
726
|
const key = entry.requiredEnvVars[0];
|
|
699
727
|
if (!key)
|
|
@@ -776,7 +804,7 @@ export async function getConfiguredEngineNameForRequest(options = {}) {
|
|
|
776
804
|
* 7. Default "anthropic" (requires ANTHROPIC_API_KEY)
|
|
777
805
|
*/
|
|
778
806
|
export async function resolveEngine(config) {
|
|
779
|
-
const { engineOption, apiKey, apiKeyEnvVar, model: _model, appId } = config;
|
|
807
|
+
const { engineOption, apiKey, apiKeyEnvVar, model: _model, appId, credentialIdentity, } = config;
|
|
780
808
|
// 1. Explicit instance passed directly
|
|
781
809
|
if (engineOption &&
|
|
782
810
|
typeof engineOption === "object" &&
|
|
@@ -792,7 +820,7 @@ export async function resolveEngine(config) {
|
|
|
792
820
|
if (!entry)
|
|
793
821
|
throw new Error(`[agent-engine] Unknown engine: "${name}". Registered: ${[..._registry.keys()].join(", ")}`);
|
|
794
822
|
assertAgentEnginePackageInstalled(entry);
|
|
795
|
-
return entry.create(await engineCreateConfigForEntry(entry, apiKey, engineConfig, false, apiKeyEnvVar));
|
|
823
|
+
return entry.create(await engineCreateConfigForEntry(entry, apiKey, engineConfig, false, apiKeyEnvVar, credentialIdentity));
|
|
796
824
|
}
|
|
797
825
|
// 3. Explicit string name from options
|
|
798
826
|
if (typeof engineOption === "string") {
|
|
@@ -800,7 +828,7 @@ export async function resolveEngine(config) {
|
|
|
800
828
|
if (!entry)
|
|
801
829
|
throw new Error(`[agent-engine] Unknown engine: "${engineOption}". Registered: ${[..._registry.keys()].join(", ")}`);
|
|
802
830
|
assertAgentEnginePackageInstalled(entry);
|
|
803
|
-
return entry.create(await engineCreateConfigForEntry(entry, apiKey, undefined, false, apiKeyEnvVar));
|
|
831
|
+
return entry.create(await engineCreateConfigForEntry(entry, apiKey, undefined, false, apiKeyEnvVar, credentialIdentity));
|
|
804
832
|
}
|
|
805
833
|
// 4. Env var — explicit engine name override
|
|
806
834
|
const envEngine = process.env.AGENT_ENGINE;
|
|
@@ -808,14 +836,17 @@ export async function resolveEngine(config) {
|
|
|
808
836
|
const entry = _registry.get(envEngine);
|
|
809
837
|
if (entry) {
|
|
810
838
|
assertAgentEnginePackageInstalled(entry);
|
|
811
|
-
return entry.create(await engineCreateConfigForEntry(entry, apiKey, undefined, true, apiKeyEnvVar));
|
|
839
|
+
return entry.create(await engineCreateConfigForEntry(entry, apiKey, undefined, true, apiKeyEnvVar, credentialIdentity));
|
|
812
840
|
}
|
|
813
841
|
}
|
|
814
842
|
const appDefault = await getAgentAppModelDefaultForCurrentRequest(appId);
|
|
815
843
|
if (appDefault?.engine) {
|
|
816
844
|
const entry = _registry.get(appDefault.engine);
|
|
817
|
-
if (entry &&
|
|
818
|
-
|
|
845
|
+
if (entry &&
|
|
846
|
+
(await isStoredEngineUsableForRequest(appDefault, entry, {
|
|
847
|
+
credentialIdentity,
|
|
848
|
+
}))) {
|
|
849
|
+
return entry.create(await engineCreateConfigForEntry(entry, apiKey, undefined, true, apiKeyEnvVar, credentialIdentity));
|
|
819
850
|
}
|
|
820
851
|
}
|
|
821
852
|
let stored = null;
|
|
@@ -829,7 +860,7 @@ export async function resolveEngine(config) {
|
|
|
829
860
|
// (Builder OAuth callback + "paste your own key" settings flow write here,
|
|
830
861
|
// not env). Stored/app defaults are checked first so an explicit provider
|
|
831
862
|
// selection can override a connected Builder account.
|
|
832
|
-
const detectedFromUser = await detectEngineFromUserSecrets();
|
|
863
|
+
const detectedFromUser = await detectEngineFromUserSecrets(credentialIdentity);
|
|
833
864
|
// 6. Settings store — only when the stored row's API key is reachable.
|
|
834
865
|
// This explicit selection beats automatic Builder detection so users can
|
|
835
866
|
// switch away from Builder credits by saving/applying their own provider key.
|
|
@@ -838,26 +869,29 @@ export async function resolveEngine(config) {
|
|
|
838
869
|
const storedConfig = storedRaw?.config;
|
|
839
870
|
if (storedRaw && typeof storedEngine === "string") {
|
|
840
871
|
const entry = _registry.get(storedEngine);
|
|
841
|
-
if (entry &&
|
|
842
|
-
|
|
872
|
+
if (entry &&
|
|
873
|
+
(await isStoredEngineUsableForRequest(storedRaw, entry, {
|
|
874
|
+
credentialIdentity,
|
|
875
|
+
}))) {
|
|
876
|
+
return entry.create(await engineCreateConfigForEntry(entry, apiKey, stripInlineApiKeyConfig(storedConfig), true, apiKeyEnvVar, credentialIdentity));
|
|
843
877
|
}
|
|
844
878
|
}
|
|
845
879
|
if (detectedFromUser) {
|
|
846
|
-
return detectedFromUser.create(await engineCreateConfigForEntry(detectedFromUser, apiKey, undefined, true, apiKeyEnvVar));
|
|
880
|
+
return detectedFromUser.create(await engineCreateConfigForEntry(detectedFromUser, apiKey, undefined, true, apiKeyEnvVar, credentialIdentity));
|
|
847
881
|
}
|
|
848
882
|
// 8. Auto-detect from any provider env var — so just dropping a key in
|
|
849
883
|
// .env works without also setting AGENT_ENGINE. Skip keys with active
|
|
850
884
|
// auth-failure markers so a rejected deploy key cannot permanently win.
|
|
851
885
|
const detected = await detectEngineFromEnvForRequest();
|
|
852
886
|
if (detected) {
|
|
853
|
-
return detected.create(await engineCreateConfigForEntry(detected, apiKey, undefined, true, apiKeyEnvVar));
|
|
887
|
+
return detected.create(await engineCreateConfigForEntry(detected, apiKey, undefined, true, apiKeyEnvVar, credentialIdentity));
|
|
854
888
|
}
|
|
855
889
|
// 9. Default: anthropic
|
|
856
890
|
const anthropicEntry = _registry.get("anthropic");
|
|
857
891
|
if (!anthropicEntry) {
|
|
858
892
|
throw new Error("[agent-engine] Default Anthropic engine is not registered. Did builtin.ts fail to load?");
|
|
859
893
|
}
|
|
860
|
-
return anthropicEntry.create(await engineCreateConfigForEntry(anthropicEntry, apiKey, undefined, true, apiKeyEnvVar));
|
|
894
|
+
return anthropicEntry.create(await engineCreateConfigForEntry(anthropicEntry, apiKey, undefined, true, apiKeyEnvVar, credentialIdentity));
|
|
861
895
|
}
|
|
862
896
|
/**
|
|
863
897
|
* Read the user-selected model for an engine from the `agent-engine` setting.
|
|
@@ -5978,6 +5978,10 @@ export function createProductionAgentHandler(options) {
|
|
|
5978
5978
|
// DIAGNOSTIC-ONLY: bracket engine resolution (Builder credential / app-default
|
|
5979
5979
|
// settings reads inside resolveEngine).
|
|
5980
5980
|
workerStep("engine_start");
|
|
5981
|
+
const credentialIdentity = {
|
|
5982
|
+
userEmail: ownerEmail,
|
|
5983
|
+
orgId: getRequestOrgId(),
|
|
5984
|
+
};
|
|
5981
5985
|
let engine;
|
|
5982
5986
|
try {
|
|
5983
5987
|
engine = await resolveEngine({
|
|
@@ -5986,6 +5990,7 @@ export function createProductionAgentHandler(options) {
|
|
|
5986
5990
|
apiKeyEnvVar: effectiveApiKeyEnvVar,
|
|
5987
5991
|
model: configuredModel,
|
|
5988
5992
|
appId: options.appId,
|
|
5993
|
+
credentialIdentity,
|
|
5989
5994
|
});
|
|
5990
5995
|
}
|
|
5991
5996
|
catch {
|
|
@@ -5993,6 +5998,7 @@ export function createProductionAgentHandler(options) {
|
|
|
5993
5998
|
apiKey: effectiveApiKey,
|
|
5994
5999
|
apiKeyEnvVar: effectiveApiKeyEnvVar,
|
|
5995
6000
|
appId: options.appId,
|
|
6001
|
+
credentialIdentity,
|
|
5996
6002
|
});
|
|
5997
6003
|
}
|
|
5998
6004
|
// DIAGNOSTIC-ONLY: engine resolution finished.
|
|
@@ -6056,6 +6062,7 @@ export function createProductionAgentHandler(options) {
|
|
|
6056
6062
|
console.log(`[agent-chat] resolved engine=${engine.name} model=${effectiveModel} requestModel=${requestModel ?? "(none)"} requestEngine=${requestEngine ?? "(none)"} modelSource=${modelSelectionSource} turnId=${requestTurnId ?? "(none)"}`);
|
|
6057
6063
|
if (!(await isResolvedEngineUsableForRequest(engine, {
|
|
6058
6064
|
apiKey: effectiveApiKey,
|
|
6065
|
+
credentialIdentity,
|
|
6059
6066
|
}))) {
|
|
6060
6067
|
setResponseHeader(event, "Content-Type", "text/event-stream");
|
|
6061
6068
|
setResponseHeader(event, "Cache-Control", "no-cache");
|
|
@@ -164,10 +164,11 @@ export declare const DEFAULT_COMPLETED_RUN_RETENTION_MS: number;
|
|
|
164
164
|
*/
|
|
165
165
|
export declare const DEFAULT_ERRORED_RUN_RETENTION_MS: number;
|
|
166
166
|
/**
|
|
167
|
-
* How recently a terminal run must have
|
|
168
|
-
* it.
|
|
169
|
-
*
|
|
170
|
-
*
|
|
167
|
+
* How recently a terminal run must have completed for `/runs/active` to surface
|
|
168
|
+
* it. This must outlast the durable browser watchdog (13 minutes) and the
|
|
169
|
+
* in-flight reader watchdog (15 minutes) so a quiet event stream can detach
|
|
170
|
+
* and still replay a run that completed while the browser was waiting.
|
|
171
|
+
* Reconnect after this window won't replay the run.
|
|
171
172
|
*/
|
|
172
173
|
export declare const TERMINAL_RUN_RECONNECT_WINDOW_MS: number;
|
|
173
174
|
/** Fast poll cadence while a SQL-backed SSE subscription is actively receiving rows. */
|
|
@@ -180,12 +180,13 @@ export const DEFAULT_COMPLETED_RUN_RETENTION_MS = 24 * 60 * 60 * 1000;
|
|
|
180
180
|
*/
|
|
181
181
|
export const DEFAULT_ERRORED_RUN_RETENTION_MS = 7 * 24 * 60 * 60 * 1000;
|
|
182
182
|
/**
|
|
183
|
-
* How recently a terminal run must have
|
|
184
|
-
* it.
|
|
185
|
-
*
|
|
186
|
-
*
|
|
183
|
+
* How recently a terminal run must have completed for `/runs/active` to surface
|
|
184
|
+
* it. This must outlast the durable browser watchdog (13 minutes) and the
|
|
185
|
+
* in-flight reader watchdog (15 minutes) so a quiet event stream can detach
|
|
186
|
+
* and still replay a run that completed while the browser was waiting.
|
|
187
|
+
* Reconnect after this window won't replay the run.
|
|
187
188
|
*/
|
|
188
|
-
export const TERMINAL_RUN_RECONNECT_WINDOW_MS =
|
|
189
|
+
export const TERMINAL_RUN_RECONNECT_WINDOW_MS = 20 * 60 * 1000;
|
|
189
190
|
/** Fast poll cadence while a SQL-backed SSE subscription is actively receiving rows. */
|
|
190
191
|
export const SQL_SUBSCRIPTION_ACTIVE_POLL_MS = 125;
|
|
191
192
|
/** Baseline SQL-backed SSE poll cadence when the run is idle. */
|
|
@@ -42,6 +42,7 @@ export interface RunClaudeCodeParticipantOptions {
|
|
|
42
42
|
prompt: string;
|
|
43
43
|
cwd: string;
|
|
44
44
|
model?: string;
|
|
45
|
+
effort?: string;
|
|
45
46
|
session?: ClaudeCodeParticipantSession;
|
|
46
47
|
/** Either the fixed CLI name or an absolute executable path for packaged apps. */
|
|
47
48
|
command?: string;
|
|
@@ -71,6 +72,6 @@ export interface ClaudeCodeSubscriptionStatusOptions {
|
|
|
71
72
|
}
|
|
72
73
|
export declare function readClaudeCodeSubscriptionStatus(options?: ClaudeCodeSubscriptionStatusOptions): Promise<ClaudeCodeSubscriptionStatus>;
|
|
73
74
|
export declare function runClaudeCodeParticipant(options: RunClaudeCodeParticipantOptions): Promise<ClaudeCodeParticipantResult>;
|
|
74
|
-
export declare function buildClaudeCodeParticipantArgs(options: Pick<RunClaudeCodeParticipantOptions, "role" | "model" | "session">): string[];
|
|
75
|
+
export declare function buildClaudeCodeParticipantArgs(options: Pick<RunClaudeCodeParticipantOptions, "role" | "model" | "effort" | "session">): string[];
|
|
75
76
|
export declare const CLAUDE_CODE_PARTICIPANT_TESTED_VERSION = "2.1.208";
|
|
76
77
|
export {};
|
|
@@ -102,6 +102,9 @@ export function buildClaudeCodeParticipantArgs(options) {
|
|
|
102
102
|
const model = readString(options.model);
|
|
103
103
|
if (model)
|
|
104
104
|
args.push("--model", model);
|
|
105
|
+
const effort = normalizeClaudeEffort(options.effort);
|
|
106
|
+
if (effort)
|
|
107
|
+
args.push("--effort", effort);
|
|
105
108
|
const sessionId = readString(options.session?.sessionId);
|
|
106
109
|
const resumeSessionId = readString(options.session?.resumeSessionId);
|
|
107
110
|
if (sessionId)
|
|
@@ -112,6 +115,22 @@ export function buildClaudeCodeParticipantArgs(options) {
|
|
|
112
115
|
args.push("--no-session-persistence");
|
|
113
116
|
return args;
|
|
114
117
|
}
|
|
118
|
+
function normalizeClaudeEffort(effort) {
|
|
119
|
+
switch (effort) {
|
|
120
|
+
case "low":
|
|
121
|
+
case "medium":
|
|
122
|
+
case "high":
|
|
123
|
+
case "max":
|
|
124
|
+
return effort;
|
|
125
|
+
case "minimal":
|
|
126
|
+
case "none":
|
|
127
|
+
return "low";
|
|
128
|
+
case "xhigh":
|
|
129
|
+
return "high";
|
|
130
|
+
default:
|
|
131
|
+
return undefined;
|
|
132
|
+
}
|
|
133
|
+
}
|
|
115
134
|
function collectClaudeCodeParticipantResult(child, options) {
|
|
116
135
|
const maxEvents = boundedLimit(options.maxEvents, DEFAULT_MAX_EVENTS);
|
|
117
136
|
const maxStreamBytes = boundedLimit(options.maxStreamBytes, DEFAULT_MAX_STREAM_BYTES);
|