@agent-native/core 0.136.0 → 0.136.2
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/.agents/skills/analysis-workspace/SKILL.md +31 -5
- package/corpus/templates/analytics/.agents/skills/dashboard-management/SKILL.md +7 -0
- package/corpus/templates/analytics/.agents/skills/gong/SKILL.md +52 -19
- package/corpus/templates/analytics/.agents/skills/provider-api/SKILL.md +25 -3
- package/corpus/templates/analytics/AGENTS.md +6 -7
- package/corpus/templates/analytics/actions/gong-calls.ts +3 -3
- package/corpus/templates/analytics/actions/provider-api-catalog.ts +1 -1
- package/corpus/templates/analytics/actions/query-agent-native-analytics.ts +1 -1
- package/corpus/templates/analytics/app/i18n-data.ts +46 -0
- package/corpus/templates/analytics/app/pages/Settings.tsx +67 -1
- package/corpus/templates/analytics/app/pages/settings/settings-search.ts +6 -0
- package/corpus/templates/analytics/changelog/2026-08-03-dashboard-builds-now-finish-without-an-extra-confirmation.md +6 -0
- package/corpus/templates/analytics/changelog/2026-08-03-error-alert-emails-are-opt-in.md +6 -0
- package/corpus/templates/analytics/server/lib/agent-chat-plan-mode.ts +10 -9
- package/corpus/templates/analytics/server/lib/error-capture.ts +38 -10
- package/corpus/templates/analytics/server/lib/gong.ts +27 -4
- package/corpus/templates/analytics/server/plugins/agent-chat.ts +64 -4
- package/corpus/templates/analytics/shared/analytics-user-prefs.ts +7 -0
- package/corpus/templates/clips/.agents/skills/video-sharing/SKILL.md +15 -6
- package/corpus/templates/clips/actions/apply-rewind-extension.ts +9 -0
- package/corpus/templates/clips/actions/cleanup-dictation.ts +2 -2
- package/corpus/templates/clips/actions/cleanup-transcript.ts +8 -11
- package/corpus/templates/clips/actions/clear-edits.ts +15 -2
- package/corpus/templates/clips/actions/create-recording-agent-link.ts +10 -0
- package/corpus/templates/clips/actions/finalize-meeting.ts +6 -6
- package/corpus/templates/clips/actions/finalize-recording.ts +33 -8
- package/corpus/templates/clips/actions/lib/ensure-seekable-video.ts +1 -3
- package/corpus/templates/clips/actions/list-recordings.ts +39 -24
- package/corpus/templates/clips/actions/list-transactional-email-ai-requests.ts +4 -1
- package/corpus/templates/clips/actions/regenerate-title.ts +2 -2
- package/corpus/templates/clips/actions/stitch-recordings.ts +36 -7
- package/corpus/templates/clips/app/components/editor/rewind-extension-dialog.tsx +1 -4
- package/corpus/templates/clips/app/components/editor/stitch-manager.tsx +3 -1
- package/corpus/templates/clips/app/components/library/recording-card.tsx +9 -0
- package/corpus/templates/clips/app/components/player/recording-views-badge.tsx +52 -7
- package/corpus/templates/clips/app/hooks/use-library.ts +1 -0
- package/corpus/templates/clips/app/hooks/use-transactional-email-bridge.ts +17 -10
- package/corpus/templates/clips/app/i18n/en-US.ts +3 -0
- package/corpus/templates/clips/app/lib/timestamp-mapping.ts +5 -0
- package/corpus/templates/clips/app/routes/r.$recordingId.tsx +1 -0
- package/corpus/templates/clips/app/routes/share.$shareId.tsx +2 -0
- package/corpus/templates/clips/changelog/2026-07-31-agent-views-now-show-the-agent-s-name-from-the-link-it-was-g.md +6 -0
- package/corpus/templates/clips/changelog/2026-07-31-clip-cards-and-the-watch-share-page-header-now-show-agent-vi.md +6 -0
- package/corpus/templates/clips/changelog/2026-08-01-recordings-stored-in-private-s3-compatible-buckets-finalize-.md +6 -0
- package/corpus/templates/clips/changelog/2026-08-03-clip-cleanup-titles-and-meeting-summaries-now-use-luna-by-de.md +6 -0
- package/corpus/templates/clips/changelog/2026-08-03-get-a-monthly-recap-email-showing-how-many-people-and-ai-age.md +6 -0
- package/corpus/templates/clips/changelog/2026-08-03-get-an-email-the-first-time-an-ai-agent-reads-one-of-your-cl.md +6 -0
- package/corpus/templates/clips/learnings.defaults.md +2 -2
- package/corpus/templates/clips/package.json +1 -1
- package/corpus/templates/clips/scripts/send-real-recap.ts +165 -0
- package/corpus/templates/clips/scripts/send-test-emails.ts +60 -0
- package/corpus/templates/clips/server/db/schema.ts +4 -0
- package/corpus/templates/clips/server/jobs/transactional-emails.ts +256 -0
- package/corpus/templates/clips/server/lib/agent-views.ts +38 -11
- package/corpus/templates/clips/server/lib/media-storage-provenance.ts +16 -0
- package/corpus/templates/clips/server/lib/public-agent-context.ts +3 -1
- package/corpus/templates/clips/server/lib/recap-metrics.ts +373 -0
- package/corpus/templates/clips/server/lib/s3-upload-provider.ts +84 -4
- package/corpus/templates/clips/server/lib/transactional-email-store.ts +41 -2
- package/corpus/templates/clips/server/lib/transactional-email-templates.ts +245 -3
- package/corpus/templates/clips/server/plugins/db.ts +16 -0
- package/corpus/templates/clips/server/register-secrets.ts +2 -2
- package/corpus/templates/clips/server/routes/api/public-recording.get.ts +7 -2
- package/corpus/templates/clips/server/routes/api/video/[recordingId].get.ts +42 -1
- package/corpus/templates/design/actions/take-design-screenshot.ts +5 -3
- package/corpus/templates/design/package.json +1 -1
- package/corpus/templates/design/server/lib/playwright-runtime.ts +13 -5
- package/corpus/templates/mail/actions/get-automation-settings.ts +4 -12
- package/corpus/templates/mail/app/pages/SettingsPage.tsx +1 -1
- package/corpus/templates/mail/changelog/2026-08-03-inbox-automations-now-prefer-the-lowest-cost-luna-model-when.md +6 -0
- package/corpus/templates/mail/server/lib/automation-engine.ts +19 -14
- package/corpus/templates/mail/server/lib/automation-model.ts +110 -0
- package/corpus/templates/mail/server/plugins/agent-chat.ts +1 -1
- package/corpus/templates/slides/actions/generate-slides-ai.ts +92 -24
- package/corpus/templates/slides/changelog/2026-08-03-legacy-slide-outlines-now-prefer-the-lowest-cost-luna-model-.md +6 -0
- package/corpus/templates/tasks/package.json +2 -2
- package/dist/agent/production-agent.d.ts +2 -2
- package/dist/agent/production-agent.js +1 -1
- package/dist/catalog.json +2 -0
- package/dist/client/AgentTaskCard.js +4 -2
- package/dist/client/i18n.js +3 -0
- package/dist/client/settings/VoiceTranscriptionSection.js +2 -2
- package/dist/collab/struct-routes.d.ts +1 -1
- package/dist/file-upload/actions/upload-image.d.ts +1 -1
- package/dist/localization/default-messages.d.ts +5 -0
- package/dist/localization/default-messages.js +5 -0
- package/dist/mcp/oauth-client-metadata.d.ts +7 -0
- package/dist/mcp/oauth-client-metadata.js +47 -0
- package/dist/mcp/oauth-route.js +7 -3
- package/dist/mcp/oauth-store.d.ts +2 -0
- package/dist/mcp/oauth-store.js +25 -3
- package/dist/observability/traces.d.ts +1 -1
- package/dist/observability/traces.js +2 -1
- package/dist/progress/routes.d.ts +1 -1
- package/dist/provider-api/index.js +29 -1
- package/dist/resources/handlers.d.ts +1 -1
- package/dist/secrets/routes.d.ts +9 -9
- package/dist/server/agent-access.d.ts +3 -1
- package/dist/server/agent-access.js +2 -1
- package/dist/server/agent-chat/browser-team-tools.d.ts +1 -0
- package/dist/server/agent-chat/browser-team-tools.js +5 -4
- package/dist/server/agent-chat/context-tools.js +2 -2
- package/dist/server/agent-chat-plugin.js +13 -6
- package/dist/server/agent-teams-run-queue.d.ts +2 -0
- package/dist/server/agent-teams.d.ts +2 -0
- package/dist/server/agent-teams.js +45 -2
- package/dist/server/request-context.d.ts +2 -0
- package/dist/server/short-lived-token.d.ts +7 -0
- package/dist/server/short-lived-token.js +7 -1
- package/dist/server/ssr-handler.js +19 -1
- package/dist/server/transcribe-voice.js +24 -16
- package/dist/vite/client.d.ts +37 -1
- package/dist/vite/client.js +110 -1
- package/docs/content/template-assets.mdx +14 -4
- package/package.json +3 -4
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import {
|
|
2
|
+
isResolvedEngineUsableForRequest,
|
|
2
3
|
registerBuiltinEngines,
|
|
3
4
|
resolveEngine,
|
|
4
5
|
} from "@agent-native/core/agent/engine";
|
|
@@ -20,6 +21,10 @@ import {
|
|
|
20
21
|
executeActions,
|
|
21
22
|
type ActionContext,
|
|
22
23
|
} from "./automation-actions.js";
|
|
24
|
+
import {
|
|
25
|
+
resolveAutomationModelSettings,
|
|
26
|
+
type AutomationModelSettings,
|
|
27
|
+
} from "./automation-model.js";
|
|
23
28
|
import {
|
|
24
29
|
createOAuth2Client,
|
|
25
30
|
gmailListMessages,
|
|
@@ -30,7 +35,6 @@ import {
|
|
|
30
35
|
} from "./google-api.js";
|
|
31
36
|
import { getOAuth2Credentials } from "./google-auth.js";
|
|
32
37
|
|
|
33
|
-
const DEFAULT_MODEL = "claude-haiku-4-5-20251001";
|
|
34
38
|
const MAX_EMAILS_PER_RUN = 50;
|
|
35
39
|
const MAX_PROCESSED_IDS = 500;
|
|
36
40
|
const PROCESSED_IDS_MAX_AGE_MS = 7 * 24 * 60 * 60 * 1000; // 7 days
|
|
@@ -338,11 +342,6 @@ interface RuleMatch {
|
|
|
338
342
|
match: boolean;
|
|
339
343
|
}
|
|
340
344
|
|
|
341
|
-
interface AutomationModelSettings {
|
|
342
|
-
engine?: string;
|
|
343
|
-
model?: string;
|
|
344
|
-
}
|
|
345
|
-
|
|
346
345
|
const MODEL_AVAILABILITY_CACHE_TTL_MS = 5 * 60 * 1000;
|
|
347
346
|
const modelAvailabilityCache = new Map<
|
|
348
347
|
string,
|
|
@@ -370,10 +369,17 @@ async function canUseAutomationModel(
|
|
|
370
369
|
!settings.engine || settings.engine === "anthropic"
|
|
371
370
|
? await resolveAnthropicKey(ownerEmail)
|
|
372
371
|
: undefined;
|
|
373
|
-
await resolveEngine({
|
|
372
|
+
const engine = await resolveEngine({
|
|
374
373
|
engineOption: settings.engine,
|
|
375
374
|
apiKey: anthropicKey,
|
|
376
375
|
});
|
|
376
|
+
if (
|
|
377
|
+
!(await isResolvedEngineUsableForRequest(engine, {
|
|
378
|
+
apiKey: anthropicKey,
|
|
379
|
+
}))
|
|
380
|
+
) {
|
|
381
|
+
throw new Error("No LLM provider is connected");
|
|
382
|
+
}
|
|
377
383
|
});
|
|
378
384
|
modelAvailabilityCache.set(cacheKey, {
|
|
379
385
|
ok: true,
|
|
@@ -482,13 +488,12 @@ async function getAutomationModelSettings(
|
|
|
482
488
|
ownerEmail: string,
|
|
483
489
|
): Promise<AutomationModelSettings> {
|
|
484
490
|
const autoSettings = await getUserSetting(ownerEmail, "automation-settings");
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
return { model: DEFAULT_MODEL };
|
|
491
|
+
return resolveAutomationModelSettings(
|
|
492
|
+
ownerEmail,
|
|
493
|
+
autoSettings && typeof autoSettings === "object"
|
|
494
|
+
? (autoSettings as AutomationModelSettings)
|
|
495
|
+
: null,
|
|
496
|
+
);
|
|
492
497
|
}
|
|
493
498
|
|
|
494
499
|
async function evaluateRules(
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
import {
|
|
2
|
+
isResolvedEngineUsableForRequest,
|
|
3
|
+
registerBuiltinEngines,
|
|
4
|
+
resolveEngine,
|
|
5
|
+
} from "@agent-native/core/agent/engine";
|
|
6
|
+
import { runWithRequestContext } from "@agent-native/core/server";
|
|
7
|
+
import { getSetting } from "@agent-native/core/settings";
|
|
8
|
+
|
|
9
|
+
export interface AutomationModelSettings {
|
|
10
|
+
engine?: string;
|
|
11
|
+
model?: string;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export const DEFAULT_AUTOMATION_ENGINE = "builder";
|
|
15
|
+
export const DEFAULT_AUTOMATION_MODEL = "gpt-5-6-luna";
|
|
16
|
+
|
|
17
|
+
const CHEAP_MODEL_CANDIDATES: AutomationModelSettings[] = [
|
|
18
|
+
{ engine: DEFAULT_AUTOMATION_ENGINE, model: DEFAULT_AUTOMATION_MODEL },
|
|
19
|
+
{ engine: "ai-sdk:openai", model: "gpt-5.6-luna" },
|
|
20
|
+
{ engine: "ai-sdk:openrouter", model: "openai/gpt-5.6-luna" },
|
|
21
|
+
];
|
|
22
|
+
|
|
23
|
+
function isUnavailableEngineError(error: unknown): boolean {
|
|
24
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
25
|
+
return /No LLM provider is connected|Connect an LLM provider|missing_credentials|not configured|not installed|unknown agent engine|engine .* unavailable/i.test(
|
|
26
|
+
message,
|
|
27
|
+
);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
async function canResolveEngine(
|
|
31
|
+
ownerEmail: string,
|
|
32
|
+
engineName: string,
|
|
33
|
+
): Promise<boolean> {
|
|
34
|
+
try {
|
|
35
|
+
registerBuiltinEngines();
|
|
36
|
+
return runWithRequestContext({ userEmail: ownerEmail }, async () => {
|
|
37
|
+
const engine = await resolveEngine({ engineOption: engineName });
|
|
38
|
+
return isResolvedEngineUsableForRequest(engine);
|
|
39
|
+
});
|
|
40
|
+
} catch (error) {
|
|
41
|
+
if (isUnavailableEngineError(error)) return false;
|
|
42
|
+
throw error;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
async function resolveEngineDefaultModel(
|
|
47
|
+
ownerEmail: string,
|
|
48
|
+
engineName: string,
|
|
49
|
+
): Promise<string> {
|
|
50
|
+
registerBuiltinEngines();
|
|
51
|
+
return runWithRequestContext({ userEmail: ownerEmail }, async () => {
|
|
52
|
+
const engine = await resolveEngine({ engineOption: engineName });
|
|
53
|
+
return engine.defaultModel;
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Prefer Luna for background text classification when a Luna-capable provider
|
|
59
|
+
* is actually configured. An app's explicit automation setting always wins;
|
|
60
|
+
* this is only the no-override default.
|
|
61
|
+
*/
|
|
62
|
+
export async function resolveDefaultAutomationModel(
|
|
63
|
+
ownerEmail: string,
|
|
64
|
+
): Promise<AutomationModelSettings> {
|
|
65
|
+
for (const candidate of CHEAP_MODEL_CANDIDATES) {
|
|
66
|
+
if (
|
|
67
|
+
candidate.engine &&
|
|
68
|
+
(await canResolveEngine(ownerEmail, candidate.engine))
|
|
69
|
+
) {
|
|
70
|
+
return candidate;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
const agentEngine = (await getSetting("agent-engine")) as {
|
|
75
|
+
engine?: string;
|
|
76
|
+
model?: string;
|
|
77
|
+
} | null;
|
|
78
|
+
if (agentEngine?.engine || agentEngine?.model) {
|
|
79
|
+
const model =
|
|
80
|
+
agentEngine.model ??
|
|
81
|
+
(agentEngine.engine
|
|
82
|
+
? await resolveEngineDefaultModel(ownerEmail, agentEngine.engine)
|
|
83
|
+
: undefined);
|
|
84
|
+
return {
|
|
85
|
+
engine: agentEngine.engine,
|
|
86
|
+
model,
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// Leave engine selection to resolveEngine so a configured non-Luna provider
|
|
91
|
+
// remains usable when none of the preferred Luna engines is connected.
|
|
92
|
+
return {};
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
export async function resolveAutomationModelSettings(
|
|
96
|
+
ownerEmail: string,
|
|
97
|
+
settings: AutomationModelSettings | null | undefined,
|
|
98
|
+
): Promise<AutomationModelSettings> {
|
|
99
|
+
const defaults = await resolveDefaultAutomationModel(ownerEmail);
|
|
100
|
+
if (!settings?.engine && !settings?.model) return defaults;
|
|
101
|
+
|
|
102
|
+
return {
|
|
103
|
+
engine: settings.engine ?? defaults.engine,
|
|
104
|
+
model:
|
|
105
|
+
settings.model ??
|
|
106
|
+
(settings.engine
|
|
107
|
+
? await resolveEngineDefaultModel(ownerEmail, settings.engine)
|
|
108
|
+
: defaults.model),
|
|
109
|
+
};
|
|
110
|
+
}
|
|
@@ -150,7 +150,7 @@ Examples:
|
|
|
150
150
|
- User says "archive marketing emails" \u2192 create rule with condition "marketing or promotional email" and action archive
|
|
151
151
|
- User says "star emails from alice@example.com" \u2192 create rule with condition "from alice@example.com" and action star
|
|
152
152
|
|
|
153
|
-
Rules are evaluated by a
|
|
153
|
+
Rules are evaluated by a low-cost text model, preferring GPT-5.6 Luna when a Luna-capable provider is available, and run every minute + when the user opens the app.
|
|
154
154
|
Use trigger-automations to force immediate processing.
|
|
155
155
|
|
|
156
156
|
Available action types: label (with labelName), archive, mark_read, star, trash.
|
|
@@ -1,11 +1,18 @@
|
|
|
1
1
|
import { defineAction } from "@agent-native/core";
|
|
2
|
-
import {
|
|
2
|
+
import { createBuilderEngine } from "@agent-native/core/agent/engine";
|
|
3
|
+
import {
|
|
4
|
+
resolveBuilderCredentials,
|
|
5
|
+
resolveSecret,
|
|
6
|
+
} from "@agent-native/core/server";
|
|
3
7
|
import type { GeneratedSlide } from "@shared/api";
|
|
4
8
|
import { z } from "zod";
|
|
5
9
|
|
|
10
|
+
const BUILDER_MODEL = "gpt-5-6-luna";
|
|
11
|
+
const GEMINI_MODEL = "gemini-2.0-flash";
|
|
12
|
+
|
|
6
13
|
export default defineAction({
|
|
7
14
|
description:
|
|
8
|
-
"Legacy helper for the Generate Slides dialog that drafts a whole new deck outline (multiple slides) from a topic. It returns markdown slide drafts, not the app's rendered slide HTML. Agent chat should create decks with create-deck slides: [] plus add-slide HTML instead of this action. Do NOT use this for a request to generate one or more images/image variations for an existing slide — use generate-image-api for that
|
|
15
|
+
"Legacy helper for the Generate Slides dialog that drafts a whole new deck outline (multiple slides) from a topic. It returns markdown slide drafts, not the app's rendered slide HTML. Agent chat should create decks with create-deck slides: [] plus add-slide HTML instead of this action. Do NOT use this for a request to generate one or more images/image variations for an existing slide — use generate-image-api for that. When Builder is connected, this uses GPT-5.6 Luna; otherwise it falls back to a user Gemini API key.",
|
|
9
16
|
schema: z.object({
|
|
10
17
|
topic: z.string().describe("Presentation topic"),
|
|
11
18
|
slideCount: z.coerce
|
|
@@ -22,15 +29,8 @@ export default defineAction({
|
|
|
22
29
|
.describe("Whether to include image prompts (default: true)"),
|
|
23
30
|
}),
|
|
24
31
|
run: async (args) => {
|
|
25
|
-
const apiKey = await resolveSecret("GEMINI_API_KEY");
|
|
26
|
-
if (!apiKey) {
|
|
27
|
-
throw new Error(
|
|
28
|
-
"Gemini API key not configured. Save GEMINI_API_KEY in settings.",
|
|
29
|
-
);
|
|
30
|
-
}
|
|
31
|
-
|
|
32
32
|
const topic = args.topic;
|
|
33
|
-
// Cap at 10. Single-shot
|
|
33
|
+
// Cap at 10. Single-shot JSON generation reliably truncates
|
|
34
34
|
// beyond that — the resulting JSON fails to parse and the user sees
|
|
35
35
|
// an error. Larger decks should be assembled with sequential
|
|
36
36
|
// `add-slide` calls from the agent chat instead.
|
|
@@ -38,9 +38,6 @@ export default defineAction({
|
|
|
38
38
|
const style = args.style;
|
|
39
39
|
const includeImages = args.includeImages !== false;
|
|
40
40
|
|
|
41
|
-
const { GoogleGenAI } = await import("@google/genai");
|
|
42
|
-
const client = new GoogleGenAI({ apiKey });
|
|
43
|
-
|
|
44
41
|
const imageInstruction = includeImages
|
|
45
42
|
? `For slides where a visual would enhance the message, set the layout to "image" and provide an "imagePrompt" field with a detailed description of what image to generate. The imagePrompt should describe a professional, high-quality image that supports the slide content. Include imagePrompt for roughly 30-40% of slides (not the title slide).`
|
|
46
43
|
: `Do not include imagePrompt fields.`;
|
|
@@ -70,17 +67,44 @@ Rules:
|
|
|
70
67
|
|
|
71
68
|
Respond ONLY with valid JSON. No markdown code fences, no explanation. Just the JSON array.`;
|
|
72
69
|
|
|
73
|
-
const
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
70
|
+
const builderCreds = await resolveBuilderCredentials();
|
|
71
|
+
const builderConfigured = Boolean(
|
|
72
|
+
builderCreds.privateKey && builderCreds.publicKey,
|
|
73
|
+
);
|
|
74
|
+
let text: string | undefined;
|
|
75
|
+
let builderError: Error | null = null;
|
|
76
|
+
|
|
77
|
+
if (builderConfigured) {
|
|
78
|
+
try {
|
|
79
|
+
text = await callBuilderGateway(prompt);
|
|
80
|
+
} catch (error) {
|
|
81
|
+
builderError =
|
|
82
|
+
error instanceof Error ? error : new Error(String(error));
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
if (!text?.trim()) {
|
|
87
|
+
const apiKey = await resolveSecret("GEMINI_API_KEY");
|
|
88
|
+
if (!apiKey) {
|
|
89
|
+
throw (
|
|
90
|
+
builderError ??
|
|
91
|
+
new Error(
|
|
92
|
+
"Slides outline generation needs Builder.io Connect or GEMINI_API_KEY.",
|
|
93
|
+
)
|
|
94
|
+
);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
const { GoogleGenAI } = await import("@google/genai");
|
|
98
|
+
const client = new GoogleGenAI({ apiKey });
|
|
99
|
+
const response = await client.models.generateContent({
|
|
100
|
+
model: GEMINI_MODEL,
|
|
101
|
+
contents: [{ text: prompt }],
|
|
102
|
+
config: {
|
|
103
|
+
responseMimeType: "application/json",
|
|
104
|
+
},
|
|
105
|
+
});
|
|
106
|
+
text = response.candidates?.[0]?.content?.parts?.[0]?.text;
|
|
107
|
+
if (!text) throw new Error("No response from Gemini");
|
|
84
108
|
}
|
|
85
109
|
|
|
86
110
|
let slides: GeneratedSlide[];
|
|
@@ -113,3 +137,47 @@ Respond ONLY with valid JSON. No markdown code fences, no explanation. Just the
|
|
|
113
137
|
return { slides };
|
|
114
138
|
},
|
|
115
139
|
});
|
|
140
|
+
|
|
141
|
+
async function callBuilderGateway(prompt: string): Promise<string> {
|
|
142
|
+
const engine = createBuilderEngine();
|
|
143
|
+
const controller = new AbortController();
|
|
144
|
+
const timeout = setTimeout(() => controller.abort(), 45_000);
|
|
145
|
+
let streamedText = "";
|
|
146
|
+
let finalText = "";
|
|
147
|
+
let terminalError: string | undefined;
|
|
148
|
+
|
|
149
|
+
try {
|
|
150
|
+
for await (const event of engine.stream({
|
|
151
|
+
model: BUILDER_MODEL,
|
|
152
|
+
systemPrompt:
|
|
153
|
+
"Return only the valid JSON requested by the user. Do not use markdown fences or commentary.",
|
|
154
|
+
messages: [{ role: "user", content: [{ type: "text", text: prompt }] }],
|
|
155
|
+
tools: [],
|
|
156
|
+
abortSignal: controller.signal,
|
|
157
|
+
maxOutputTokens: 16_000,
|
|
158
|
+
temperature: 0,
|
|
159
|
+
})) {
|
|
160
|
+
if (event.type === "text-delta") streamedText += event.text;
|
|
161
|
+
if (event.type === "assistant-content") {
|
|
162
|
+
finalText = event.parts
|
|
163
|
+
.filter((part) => part.type === "text")
|
|
164
|
+
.map((part) => part.text)
|
|
165
|
+
.join("")
|
|
166
|
+
.trim();
|
|
167
|
+
}
|
|
168
|
+
if (event.type === "stop" && event.reason === "error") {
|
|
169
|
+
terminalError = event.error ?? "Builder gateway returned an error";
|
|
170
|
+
}
|
|
171
|
+
if (event.type === "stop" && event.reason === "max_tokens") {
|
|
172
|
+
terminalError = "Builder gateway truncated the slide outline";
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
} finally {
|
|
176
|
+
clearTimeout(timeout);
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
if (terminalError) throw new Error(terminalError);
|
|
180
|
+
const text = (finalText || streamedText).trim();
|
|
181
|
+
if (!text) throw new Error("Builder gateway returned no slide outline");
|
|
182
|
+
return text;
|
|
183
|
+
}
|
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
"db:generate": "drizzle-kit generate",
|
|
17
17
|
"test": "vitest --run --config vitest.config.ts",
|
|
18
18
|
"test:e2e": "playwright test",
|
|
19
|
-
"test:e2e:install": "playwright install chromium",
|
|
19
|
+
"test:e2e:install": "playwright install --only-shell chromium",
|
|
20
20
|
"test:all": "pnpm test && pnpm test:e2e"
|
|
21
21
|
},
|
|
22
22
|
"dependencies": {
|
|
@@ -36,7 +36,7 @@
|
|
|
36
36
|
"zod": "^4.4.3"
|
|
37
37
|
},
|
|
38
38
|
"devDependencies": {
|
|
39
|
-
"@playwright/test": "
|
|
39
|
+
"@playwright/test": "catalog:",
|
|
40
40
|
"@radix-ui/react-accordion": "^1.2.12",
|
|
41
41
|
"@radix-ui/react-alert-dialog": "^1.1.15",
|
|
42
42
|
"@radix-ui/react-aspect-ratio": "^1.1.8",
|
|
@@ -273,8 +273,8 @@ export interface ProductionAgentOptions {
|
|
|
273
273
|
* `AGENT_CHAT_DURABLE_BACKGROUND` flag so the background function is emitted.
|
|
274
274
|
*/
|
|
275
275
|
durableBackgroundRuns?: boolean;
|
|
276
|
-
/** Called when a run starts, with the send function
|
|
277
|
-
onRunStart?: (send: (event: AgentChatEvent) => void, threadId: string) => void | Promise<void>;
|
|
276
|
+
/** Called when a run starts, with the send function, threadId, and runId. */
|
|
277
|
+
onRunStart?: (send: (event: AgentChatEvent) => void, threadId: string, runId: string) => void | Promise<void>;
|
|
278
278
|
/**
|
|
279
279
|
* Called after the engine + model are resolved for this request. Used by
|
|
280
280
|
* the plugin layer to thread the parent's choices into sub-agents so
|
|
@@ -6870,7 +6870,7 @@ export function createProductionAgentHandler(options) {
|
|
|
6870
6870
|
}
|
|
6871
6871
|
// Notify listeners that a run has started (used by agent teams)
|
|
6872
6872
|
if (options.onRunStart) {
|
|
6873
|
-
await options.onRunStart(send, threadId ?? runId);
|
|
6873
|
+
await options.onRunStart(send, threadId ?? runId, runId);
|
|
6874
6874
|
}
|
|
6875
6875
|
// Resolve custom workspace agent mentions first.
|
|
6876
6876
|
if (customAgentRefs.length > 0) {
|
package/dist/catalog.json
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"@assistant-ui/react": "^0.12.19",
|
|
3
|
+
"@playwright/test": "1.61.1",
|
|
3
4
|
"@radix-ui/react-dialog": "^1.1.19",
|
|
4
5
|
"@radix-ui/react-dropdown-menu": "^2.1.20",
|
|
5
6
|
"@radix-ui/react-hover-card": "^1.1.19",
|
|
@@ -40,6 +41,7 @@
|
|
|
40
41
|
"lowlight": "^3.3.0",
|
|
41
42
|
"next-themes": "^0.4.6",
|
|
42
43
|
"oxfmt": "0.56.0",
|
|
44
|
+
"playwright": "1.61.1",
|
|
43
45
|
"react": "^19.2.7",
|
|
44
46
|
"react-dom": "^19.2.7",
|
|
45
47
|
"recharts": "^3.9.2",
|
|
@@ -4,12 +4,14 @@ import { useState, useEffect, useRef, useCallback } from "react";
|
|
|
4
4
|
import ReactMarkdown from "react-markdown";
|
|
5
5
|
import remarkGfm from "remark-gfm";
|
|
6
6
|
import { agentNativePath } from "./api-path.js";
|
|
7
|
+
import { useT } from "./i18n.js";
|
|
7
8
|
import { cn } from "./utils.js";
|
|
8
9
|
/**
|
|
9
10
|
* Rich preview card for a sub-agent task. Listens for agent-task-event
|
|
10
11
|
* CustomEvents to update its state in real-time.
|
|
11
12
|
*/
|
|
12
13
|
export function AgentTaskCard({ taskId, threadId, description, onOpen, }) {
|
|
14
|
+
const t = useT();
|
|
13
15
|
const [expanded, setExpanded] = useState(true);
|
|
14
16
|
const [status, setStatus] = useState("running");
|
|
15
17
|
const [preview, setPreview] = useState("");
|
|
@@ -142,8 +144,8 @@ export function AgentTaskCard({ taskId, threadId, description, onOpen, }) {
|
|
|
142
144
|
? "border-destructive/30"
|
|
143
145
|
: isComplete
|
|
144
146
|
? "border-emerald-500/20"
|
|
145
|
-
: "border-border"), children: [_jsxs("button", { type: "button", "aria-expanded": expanded, className: "flex w-full items-center gap-2 px-3 py-2 text-start transition-colors hover:bg-muted/45", onClick: () => setExpanded(!expanded), children: [_jsxs("span", { className: "inline-flex h-5 shrink-0 items-center gap-1 rounded-md border border-border/60 bg-background/70 px-1.5 text-[10px] font-medium text-muted-foreground", children: [_jsx(IconSubtask, { className: "h-3 w-3" }), "
|
|
147
|
+
: "border-border"), children: [_jsxs("button", { type: "button", "aria-expanded": expanded, className: "flex w-full items-center gap-2 px-3 py-2 text-start transition-colors hover:bg-muted/45", onClick: () => setExpanded(!expanded), children: [_jsxs("span", { className: "inline-flex h-5 shrink-0 items-center gap-1 rounded-md border border-border/60 bg-background/70 px-1.5 text-[10px] font-medium text-muted-foreground", children: [_jsx(IconSubtask, { className: "h-3 w-3" }), t("agentTask.backgroundTask")] }), _jsx("span", { className: "min-w-0 flex-1 truncate text-xs font-medium text-foreground", children: taskTitle }), _jsxs("span", { className: statusClassName, children: [isRunning ? (_jsx(IconLoader2, { className: "h-3 w-3 animate-spin" })) : isError ? (_jsx(IconAlertCircle, { className: "h-3 w-3" })) : (_jsx(IconCheck, { className: "h-3 w-3" })), statusLabel] }), _jsx(IconChevronRight, { className: cn("h-3 w-3 shrink-0 text-muted-foreground/40 transition-transform duration-150", expanded && "rotate-90", "rtl:-scale-x-100") })] }), expanded && isRunning && currentStepText && (_jsxs("div", { className: "flex gap-1.5 border-t border-border/60 px-3 py-1.5 text-[11px] text-muted-foreground", children: [_jsx("span", { className: "shrink-0 font-medium text-foreground/70", children: "Now:" }), _jsx("span", { className: "min-w-0 break-words", children: currentStepText })] })), expanded && hasContent && (_jsx("div", { className: "px-3 pb-2 pt-1", children: _jsx("div", { ref: previewRef, className: "agent-markdown prose prose-sm prose-invert max-h-48 max-w-none overflow-y-auto break-words rounded-md border border-border/50 bg-muted/25 px-3 py-2 text-xs text-muted-foreground", children: _jsx(ReactMarkdown, { remarkPlugins: [remarkGfm], children: displayText.length > 800
|
|
146
148
|
? "..." + displayText.slice(-800)
|
|
147
|
-
: displayText }) }) })), expanded && (_jsxs("div", { className: "flex items-center justify-between gap-2 px-3 pb-2", children: [_jsx("span", { className: "min-w-0 flex-1 truncate text-[10px] text-muted-foreground/60", children: !hasContent ? emptyMessage : "" }), _jsxs("div", { className: "flex shrink-0 items-center gap-1", children: [isRunning && (_jsxs("button", { onClick: handleStop, className: "inline-flex cursor-pointer items-center gap-1 rounded-md px-2 py-1 text-[11px] font-medium text-muted-foreground hover:bg-muted hover:text-destructive", "aria-label": "
|
|
149
|
+
: displayText }) }) })), expanded && (_jsxs("div", { className: "flex items-center justify-between gap-2 px-3 pb-2", children: [_jsx("span", { className: "min-w-0 flex-1 truncate text-[10px] text-muted-foreground/60", children: !hasContent ? emptyMessage : "" }), _jsxs("div", { className: "flex shrink-0 items-center gap-1", children: [isRunning && (_jsxs("button", { onClick: handleStop, className: "inline-flex cursor-pointer items-center gap-1 rounded-md px-2 py-1 text-[11px] font-medium text-muted-foreground hover:bg-muted hover:text-destructive", "aria-label": t("agentTask.stop"), children: [_jsx(IconPlayerStop, { className: "h-3 w-3" }), "Stop"] })), _jsxs("button", { onClick: handleOpen, className: "inline-flex cursor-pointer items-center gap-1 rounded-md px-2 py-1 text-[11px] font-medium text-muted-foreground hover:bg-muted hover:text-foreground", children: [t("agentTask.openThread"), _jsx(IconExternalLink, { className: "h-3 w-3" })] })] })] }))] }));
|
|
148
150
|
}
|
|
149
151
|
//# sourceMappingURL=AgentTaskCard.js.map
|
package/dist/client/i18n.js
CHANGED
|
@@ -349,6 +349,9 @@ const CORE_FALLBACK_MESSAGES = {
|
|
|
349
349
|
"runsTray.finishedHours": "Finished {{count}}h ago",
|
|
350
350
|
"runsTray.updatedDate": "Updated {{date}}",
|
|
351
351
|
"runsTray.finishedDate": "Finished {{date}}",
|
|
352
|
+
"agentTask.backgroundTask": "Background task",
|
|
353
|
+
"agentTask.stop": "Stop background task",
|
|
354
|
+
"agentTask.openThread": "Open task thread",
|
|
352
355
|
"codeRequired.fallbackDetail": "Edit locally or use Builder.io to edit this code in the cloud and continue customizing the app any way you like.",
|
|
353
356
|
"codeRequired.defaultFeature": "Make the requested code changes to this app",
|
|
354
357
|
"codeRequired.branchError": "Failed to create branch",
|
|
@@ -292,8 +292,8 @@ export function VoiceTranscriptionSection() {
|
|
|
292
292
|
e.stopPropagation();
|
|
293
293
|
focusKey("GOOGLE_APPLICATION_CREDENTIALS");
|
|
294
294
|
}, className: "inline-flex items-center gap-1 rounded border border-border px-2 py-0.5 text-[10px] text-muted-foreground hover:text-foreground hover:bg-accent/40", children: ["Configure", _jsx(IconExternalLink, { size: 10 })] })) }), _jsx(ProviderOption, { id: "auto", selected: transcriptionMode === "batch" && provider === "auto", onSelect: () => chooseBatchProvider("auto"), title: "Automatic batch fallback", subtitle: "Keep the current Clips fallback chain: Builder Gemini, Gemini, Groq, then OpenAI." }), _jsx(ProviderOption, { id: "builder-gemini", selected: transcriptionMode === "batch" && provider === "builder-gemini", onSelect: () => chooseBatchProvider("builder-gemini"), disabled: !builderStatus?.configured, title: "Builder.io Connect", subtitle: builderStatus?.configured
|
|
295
|
-
? "Use Builder-hosted Gemini Flash-Lite for batch transcription and cleanup."
|
|
296
|
-
: "One-click connect for Gemini Flash-Lite
|
|
295
|
+
? "Use Builder-hosted Gemini Flash-Lite for batch transcription and Luna for text cleanup."
|
|
296
|
+
: "One-click connect for Gemini Flash-Lite transcription and Luna text cleanup. No Google key needed.", rightSlot: builderStatus?.configured ? (_jsxs("span", { className: "flex items-center gap-1 text-[10px] text-green-500", children: [_jsx(IconCheck, { size: 10 }), "Connected"] })) : (_jsxs("button", { type: "button", onClick: (e) => {
|
|
297
297
|
e.stopPropagation();
|
|
298
298
|
openBuilderConnect();
|
|
299
299
|
}, className: "inline-flex items-center gap-1 rounded border border-border px-2 py-0.5 text-[10px] text-muted-foreground hover:text-foreground hover:bg-accent/40", children: ["Connect Builder.io", _jsx(IconExternalLink, { size: 10 })] })) }), _jsx(ProviderOption, { id: "gemini", selected: transcriptionMode === "batch" && provider === "gemini", onSelect: () => chooseBatchProvider("gemini"), title: "Google Gemini", subtitle: "BYOK Gemini for AI cleanup and optional strict batch transcription.", rightSlot: geminiConfigured === null ? null : geminiConfigured ? (_jsxs("span", { className: "flex items-center gap-1 text-[10px] text-green-500", children: [_jsx(IconCheck, { size: 10 }), "Key set"] })) : (_jsxs("button", { type: "button", onClick: (e) => {
|
|
@@ -13,8 +13,8 @@
|
|
|
13
13
|
* Body: { json: any, fieldName?: string, type?: "map"|"array", requestSource?: string }
|
|
14
14
|
*/
|
|
15
15
|
export declare const postCollabJson: import("h3").EventHandlerWithFetch<import("h3").EventHandlerRequest, Promise<{
|
|
16
|
-
error: string;
|
|
17
16
|
ok?: undefined;
|
|
17
|
+
error: string;
|
|
18
18
|
} | {
|
|
19
19
|
error?: undefined;
|
|
20
20
|
ok: boolean;
|
|
@@ -17,12 +17,12 @@ declare const _default: import("../../action.js").ActionDefinition<{
|
|
|
17
17
|
id?: undefined;
|
|
18
18
|
provider?: undefined;
|
|
19
19
|
} | {
|
|
20
|
-
error?: undefined;
|
|
21
20
|
configured?: undefined;
|
|
22
21
|
connectPath?: undefined;
|
|
23
22
|
url: string;
|
|
24
23
|
id: string;
|
|
25
24
|
provider: string;
|
|
25
|
+
error?: undefined;
|
|
26
26
|
}>;
|
|
27
27
|
export default _default;
|
|
28
28
|
//# sourceMappingURL=upload-image.d.ts.map
|
|
@@ -637,6 +637,11 @@ declare const messages: {
|
|
|
637
637
|
updatedDate: string;
|
|
638
638
|
finishedDate: string;
|
|
639
639
|
};
|
|
640
|
+
agentTask: {
|
|
641
|
+
backgroundTask: string;
|
|
642
|
+
stop: string;
|
|
643
|
+
openThread: string;
|
|
644
|
+
};
|
|
640
645
|
codeRequired: {
|
|
641
646
|
fallbackDetail: string;
|
|
642
647
|
defaultFeature: string;
|
|
@@ -642,6 +642,11 @@ const messages = {
|
|
|
642
642
|
updatedDate: "Updated {{date}}",
|
|
643
643
|
finishedDate: "Finished {{date}}",
|
|
644
644
|
},
|
|
645
|
+
agentTask: {
|
|
646
|
+
backgroundTask: "Background task",
|
|
647
|
+
stop: "Stop background task",
|
|
648
|
+
openThread: "Open task thread",
|
|
649
|
+
},
|
|
645
650
|
codeRequired: {
|
|
646
651
|
fallbackDetail: "Edit locally or use Builder.io to edit this code in the cloud and continue customizing the app any way you like.",
|
|
647
652
|
defaultFeature: "Make the requested code changes to this app",
|
|
@@ -10,6 +10,13 @@ export interface OAuthClientMetadataDocument {
|
|
|
10
10
|
export declare function isUrlBasedOAuthClientId(clientId: string): boolean;
|
|
11
11
|
export declare function validateOAuthClientMetadataUrl(clientId: string): URL;
|
|
12
12
|
export declare function isAllowedOAuthRedirectUri(value: unknown): value is string;
|
|
13
|
+
/**
|
|
14
|
+
* RFC 8252 section 7.3: a native client's loopback redirect URI uses a port
|
|
15
|
+
* chosen at request time, so the authorization server must ignore the port when
|
|
16
|
+
* comparing loopback redirect URIs against the registered set. Non-loopback
|
|
17
|
+
* redirects keep exact matching, so this cannot widen into an open redirect.
|
|
18
|
+
*/
|
|
19
|
+
export declare function matchesRegisteredRedirectUri(registered: readonly string[], redirectUri: string, applicationType: "native" | "web"): boolean;
|
|
13
20
|
export declare function applicationTypeForRedirectUris(redirectUris: string[]): "native" | "web";
|
|
14
21
|
export declare function resolveOAuthClientMetadataDocument(clientId: string): Promise<OAuthClientMetadataDocument>;
|
|
15
22
|
//# sourceMappingURL=oauth-client-metadata.d.ts.map
|
|
@@ -73,6 +73,53 @@ export function isAllowedOAuthRedirectUri(value) {
|
|
|
73
73
|
return false;
|
|
74
74
|
}
|
|
75
75
|
}
|
|
76
|
+
function isLoopbackRedirectHost(hostname) {
|
|
77
|
+
return (hostname === "localhost" ||
|
|
78
|
+
hostname === "127.0.0.1" ||
|
|
79
|
+
hostname === "::1" ||
|
|
80
|
+
hostname === "[::1]");
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* RFC 8252 section 7.3: a native client's loopback redirect URI uses a port
|
|
84
|
+
* chosen at request time, so the authorization server must ignore the port when
|
|
85
|
+
* comparing loopback redirect URIs against the registered set. Non-loopback
|
|
86
|
+
* redirects keep exact matching, so this cannot widen into an open redirect.
|
|
87
|
+
*/
|
|
88
|
+
export function matchesRegisteredRedirectUri(registered, redirectUri, applicationType) {
|
|
89
|
+
let candidate;
|
|
90
|
+
try {
|
|
91
|
+
candidate = new URL(redirectUri);
|
|
92
|
+
// coercion-ok: a malformed candidate is an invalid redirect, not a runtime failure.
|
|
93
|
+
}
|
|
94
|
+
catch {
|
|
95
|
+
return false;
|
|
96
|
+
}
|
|
97
|
+
if (candidate.hash || candidate.username || candidate.password)
|
|
98
|
+
return false;
|
|
99
|
+
if (registered.includes(redirectUri))
|
|
100
|
+
return true;
|
|
101
|
+
if (applicationType !== "native" ||
|
|
102
|
+
candidate.protocol !== "http:" ||
|
|
103
|
+
!isLoopbackRedirectHost(candidate.hostname)) {
|
|
104
|
+
return false;
|
|
105
|
+
}
|
|
106
|
+
return registered.some((value) => {
|
|
107
|
+
let allowed;
|
|
108
|
+
try {
|
|
109
|
+
allowed = new URL(value);
|
|
110
|
+
// coercion-ok: a malformed registration cannot match a valid redirect.
|
|
111
|
+
}
|
|
112
|
+
catch {
|
|
113
|
+
return false;
|
|
114
|
+
}
|
|
115
|
+
if (allowed.hash || allowed.username || allowed.password)
|
|
116
|
+
return false;
|
|
117
|
+
return (allowed.protocol === candidate.protocol &&
|
|
118
|
+
allowed.hostname === candidate.hostname &&
|
|
119
|
+
allowed.pathname === candidate.pathname &&
|
|
120
|
+
allowed.search === candidate.search);
|
|
121
|
+
});
|
|
122
|
+
}
|
|
76
123
|
export function applicationTypeForRedirectUris(redirectUris) {
|
|
77
124
|
return redirectUris.some((value) => {
|
|
78
125
|
const url = new URL(value);
|
package/dist/mcp/oauth-route.js
CHANGED
|
@@ -13,7 +13,7 @@ import { getOrgDomain } from "../org/context.js";
|
|
|
13
13
|
import { getConfiguredLoginHtml, getSession } from "../server/auth.js";
|
|
14
14
|
import { getAuthSecret } from "../server/better-auth-instance.js";
|
|
15
15
|
import { readBody } from "../server/h3-helpers.js";
|
|
16
|
-
import { applicationTypeForRedirectUris, isAllowedOAuthRedirectUri, isUrlBasedOAuthClientId, resolveOAuthClientMetadataDocument, } from "./oauth-client-metadata.js";
|
|
16
|
+
import { applicationTypeForRedirectUris, isAllowedOAuthRedirectUri, isUrlBasedOAuthClientId, matchesRegisteredRedirectUri, resolveOAuthClientMetadataDocument, } from "./oauth-client-metadata.js";
|
|
17
17
|
import { createOAuthCode, createOAuthRefreshToken, consumeOAuthCode, generateOpaqueToken, getOAuthClient, getOAuthCode, getOAuthRefreshToken, registerOAuthClient, touchOAuthRefreshToken, } from "./oauth-store.js";
|
|
18
18
|
import { MCP_OAUTH_ACCESS_TOKEN_TTL_SECONDS, MCP_OAUTH_SCOPES, normalizeOAuthScope, signMcpOAuthAccessToken, } from "./oauth-token.js";
|
|
19
19
|
import { MCP_PUBLIC_ROUTE_PREFIX, MCP_ROUTE_PREFIXES } from "./route-paths.js";
|
|
@@ -291,7 +291,9 @@ async function handleRegister(event) {
|
|
|
291
291
|
requestedApplicationType !== "web") {
|
|
292
292
|
return oauthError("invalid_client_metadata", "application_type must be native or web");
|
|
293
293
|
}
|
|
294
|
-
const applicationType = requestedApplicationType
|
|
294
|
+
const applicationType = requestedApplicationType === "native" || requestedApplicationType === "web"
|
|
295
|
+
? requestedApplicationType
|
|
296
|
+
: applicationTypeForRedirectUris(redirectUris);
|
|
295
297
|
const clientName = typeof body.client_name === "string"
|
|
296
298
|
? body.client_name.trim().slice(0, 120)
|
|
297
299
|
: null;
|
|
@@ -303,6 +305,7 @@ async function handleRegister(event) {
|
|
|
303
305
|
grantTypes: grantTypes.length ? grantTypes : undefined,
|
|
304
306
|
responseTypes: responseTypes.length ? responseTypes : undefined,
|
|
305
307
|
tokenEndpointAuthMethod: method,
|
|
308
|
+
applicationType,
|
|
306
309
|
});
|
|
307
310
|
}
|
|
308
311
|
catch (err) {
|
|
@@ -555,7 +558,8 @@ async function handleAuthorize(event, options) {
|
|
|
555
558
|
const client = isUrlBasedOAuthClientId(clientId)
|
|
556
559
|
? await resolveOAuthClientMetadataDocument(clientId).catch(() => null)
|
|
557
560
|
: await getOAuthClient(clientId);
|
|
558
|
-
if (!client ||
|
|
561
|
+
if (!client ||
|
|
562
|
+
!matchesRegisteredRedirectUri(client.redirectUris, redirectUri, client.applicationType)) {
|
|
559
563
|
return oauthError("invalid_client", "Unknown client or redirect_uri");
|
|
560
564
|
}
|
|
561
565
|
const session = await getSession(event);
|
|
@@ -26,6 +26,7 @@ export interface OAuthClientRow {
|
|
|
26
26
|
grantTypes: string[];
|
|
27
27
|
responseTypes: string[];
|
|
28
28
|
tokenEndpointAuthMethod: string;
|
|
29
|
+
applicationType: "native" | "web";
|
|
29
30
|
createdAt: number | null;
|
|
30
31
|
}
|
|
31
32
|
export interface OAuthCodeRow {
|
|
@@ -66,6 +67,7 @@ export declare function registerOAuthClient(params: {
|
|
|
66
67
|
grantTypes?: string[];
|
|
67
68
|
responseTypes?: string[];
|
|
68
69
|
tokenEndpointAuthMethod?: string;
|
|
70
|
+
applicationType?: "native" | "web";
|
|
69
71
|
}): Promise<OAuthClientRow>;
|
|
70
72
|
export declare function getOAuthClient(clientId: string): Promise<OAuthClientRow | null>;
|
|
71
73
|
export declare function createOAuthCode(params: {
|