@agent-native/core 0.118.1 → 0.119.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 +2 -2
- package/corpus/core/CHANGELOG.md +19 -0
- package/corpus/core/docs/content/a2a-protocol.mdx +15 -0
- package/corpus/core/docs/content/template-chat.mdx +1 -1
- package/corpus/core/docs/content/template-plan.mdx +3 -3
- package/corpus/core/package.json +1 -1
- package/corpus/core/src/a2a/activity.ts +371 -0
- package/corpus/core/src/a2a/client.ts +3 -1
- package/corpus/core/src/a2a/index.ts +21 -0
- package/corpus/core/src/a2a/types.ts +8 -0
- package/corpus/core/src/agent/model-config.ts +19 -23
- package/corpus/core/src/agent/production-agent.ts +167 -100
- package/corpus/core/src/agent/types.ts +16 -1
- package/corpus/core/src/client/chat/tool-call-display.tsx +190 -35
- package/corpus/core/src/client/chat-model-groups.ts +67 -27
- package/corpus/core/src/client/sharing/ShareButton.tsx +1 -27
- package/corpus/core/src/client/sharing/ShareDialog.tsx +0 -16
- package/corpus/core/src/client/sharing/useShareButtonController.ts +0 -7
- package/corpus/core/src/client/sharing/useShareDialogController.ts +0 -2
- package/corpus/core/src/client/sse-event-processor.ts +85 -1
- package/corpus/core/src/ingestion/figma-node-to-html.ts +20 -99
- package/corpus/core/src/ingestion/figma-paint-math.ts +371 -0
- package/corpus/core/src/ingestion/index.ts +20 -0
- package/corpus/core/src/localization/default-messages.ts +6 -0
- package/corpus/core/src/scripts/agent-engines/list-agent-engines.ts +1 -18
- package/corpus/core/src/scripts/call-agent.ts +49 -6
- package/corpus/core/src/server/agent-chat-plugin.ts +50 -10
- package/corpus/core/src/templates/workspace-core/.agents/skills/a2a-protocol/SKILL.md +16 -0
- package/corpus/templates/assets/app/routes/library.tsx +17 -9
- package/corpus/templates/clips/changelog/2026-07-23-fixed-recordings-failing-to-save-with-a-cancelled-or-cleanup.md +6 -0
- package/corpus/templates/clips/server/plugins/db.ts +9 -1
- package/corpus/templates/design/AGENTS.md +18 -5
- package/corpus/templates/design/FIGMA_INTEROPERABILITY.md +25 -12
- package/corpus/templates/design/actions/hydrate-figma-paste-images.ts +115 -0
- package/corpus/templates/design/actions/import-figma-clipboard.ts +83 -18
- package/corpus/templates/design/actions/import-figma-frame.ts +34 -21
- package/corpus/templates/design/app/components/design/DesignCanvas.tsx +28 -1
- package/corpus/templates/design/app/components/design/DesignImportPanel.tsx +106 -11
- package/corpus/templates/design/app/components/design/FigmaHydrationDialog.tsx +273 -0
- package/corpus/templates/design/app/components/design/MultiScreenCanvas.tsx +7 -7
- package/corpus/templates/design/app/components/design/bridge/editor-chrome.bridge.ts +57 -9
- package/corpus/templates/design/app/components/design/design-canvas/iframe-events.ts +10 -0
- package/corpus/templates/design/app/components/design/multi-screen/board-surface-html.ts +8 -1
- package/corpus/templates/design/app/components/design/multi-screen/types.ts +3 -0
- package/corpus/templates/design/app/i18n-data.ts +150 -2
- package/corpus/templates/design/app/lib/design-file-upload.ts +85 -0
- package/corpus/templates/design/app/lib/design-import.ts +19 -1
- package/corpus/templates/design/app/lib/figma-clipboard.ts +110 -10
- package/corpus/templates/design/changelog/2026-07-20-paste-figma-frames-directly-onto-the-canvas-no-token-needed.md +6 -0
- package/corpus/templates/design/changelog/2026-07-21-fig-upload-frame-url-and-rate-limit-errors.md +5 -0
- package/corpus/templates/design/changelog/2026-07-22-connecting-a-figma-token-now-actually-fills-in-a-pasted-desi.md +6 -0
- package/corpus/templates/design/changelog/2026-07-22-figma-line-arrow-vectors-and-stroked-icons-now-render-instea.md +6 -0
- package/corpus/templates/design/changelog/2026-07-22-fill-a-no-token-figma-paste-s-images-by-dropping-the-origina.md +6 -0
- package/corpus/templates/design/changelog/2026-07-22-imported-and-pasted-figma-frames-now-render-faithfully-fixed.md +6 -0
- package/corpus/templates/design/server/handlers/import-design-file.ts +50 -0
- package/corpus/templates/design/server/lib/fig-file-decoder.ts +90 -49
- package/corpus/templates/design/server/lib/fig-file-import.ts +19 -5
- package/corpus/templates/design/server/lib/fig-file-to-html.ts +583 -78
- package/corpus/templates/design/server/lib/figma-clipboard-local-decode.ts +238 -0
- package/corpus/templates/design/server/lib/figma-image-hydration.ts +389 -0
- package/corpus/templates/design/server/lib/figma-node-import.ts +136 -36
- package/corpus/templates/design/server/register-secrets.ts +14 -2
- package/dist/a2a/activity.d.ts +50 -0
- package/dist/a2a/activity.d.ts.map +1 -0
- package/dist/a2a/activity.js +236 -0
- package/dist/a2a/activity.js.map +1 -0
- package/dist/a2a/client.d.ts +2 -0
- package/dist/a2a/client.d.ts.map +1 -1
- package/dist/a2a/client.js +1 -1
- package/dist/a2a/client.js.map +1 -1
- package/dist/a2a/index.d.ts +2 -1
- package/dist/a2a/index.d.ts.map +1 -1
- package/dist/a2a/index.js +1 -0
- package/dist/a2a/index.js.map +1 -1
- package/dist/a2a/types.d.ts +1 -0
- package/dist/a2a/types.d.ts.map +1 -1
- package/dist/a2a/types.js.map +1 -1
- package/dist/agent/engine/anthropic-engine.d.ts +1 -1
- package/dist/agent/engine/builder-engine.d.ts +2 -2
- package/dist/agent/engine/builder-engine.d.ts.map +1 -1
- package/dist/agent/model-config.d.ts +16 -16
- package/dist/agent/model-config.d.ts.map +1 -1
- package/dist/agent/model-config.js +19 -23
- package/dist/agent/model-config.js.map +1 -1
- package/dist/agent/production-agent.d.ts +26 -0
- package/dist/agent/production-agent.d.ts.map +1 -1
- package/dist/agent/production-agent.js +137 -80
- package/dist/agent/production-agent.js.map +1 -1
- package/dist/agent/types.d.ts +10 -0
- package/dist/agent/types.d.ts.map +1 -1
- package/dist/agent/types.js.map +1 -1
- package/dist/client/chat/tool-call-display.d.ts.map +1 -1
- package/dist/client/chat/tool-call-display.js +63 -14
- package/dist/client/chat/tool-call-display.js.map +1 -1
- package/dist/client/chat-model-groups.d.ts.map +1 -1
- package/dist/client/chat-model-groups.js +44 -21
- package/dist/client/chat-model-groups.js.map +1 -1
- package/dist/client/sharing/ShareButton.d.ts +1 -1
- package/dist/client/sharing/ShareButton.d.ts.map +1 -1
- package/dist/client/sharing/ShareButton.js +3 -4
- package/dist/client/sharing/ShareButton.js.map +1 -1
- package/dist/client/sharing/ShareDialog.d.ts.map +1 -1
- package/dist/client/sharing/ShareDialog.js +1 -2
- package/dist/client/sharing/ShareDialog.js.map +1 -1
- package/dist/client/sharing/useShareButtonController.d.ts +0 -1
- package/dist/client/sharing/useShareButtonController.d.ts.map +1 -1
- package/dist/client/sharing/useShareButtonController.js +0 -6
- package/dist/client/sharing/useShareButtonController.js.map +1 -1
- package/dist/client/sharing/useShareDialogController.d.ts +0 -1
- package/dist/client/sharing/useShareDialogController.d.ts.map +1 -1
- package/dist/client/sharing/useShareDialogController.js +0 -1
- package/dist/client/sharing/useShareDialogController.js.map +1 -1
- package/dist/client/sse-event-processor.d.ts +12 -0
- package/dist/client/sse-event-processor.d.ts.map +1 -1
- package/dist/client/sse-event-processor.js +63 -1
- package/dist/client/sse-event-processor.js.map +1 -1
- package/dist/collab/routes.d.ts +1 -1
- package/dist/ingestion/figma-node-to-html.d.ts.map +1 -1
- package/dist/ingestion/figma-node-to-html.js +14 -88
- package/dist/ingestion/figma-node-to-html.js.map +1 -1
- package/dist/ingestion/figma-paint-math.d.ts +157 -0
- package/dist/ingestion/figma-paint-math.d.ts.map +1 -0
- package/dist/ingestion/figma-paint-math.js +274 -0
- package/dist/ingestion/figma-paint-math.js.map +1 -0
- package/dist/ingestion/index.d.ts +1 -0
- package/dist/ingestion/index.d.ts.map +1 -1
- package/dist/ingestion/index.js +1 -0
- package/dist/ingestion/index.js.map +1 -1
- package/dist/localization/default-messages.d.ts +6 -0
- package/dist/localization/default-messages.d.ts.map +1 -1
- package/dist/localization/default-messages.js +6 -0
- package/dist/localization/default-messages.js.map +1 -1
- package/dist/notifications/routes.d.ts +2 -2
- package/dist/observability/routes.d.ts +5 -5
- package/dist/progress/routes.d.ts +1 -1
- package/dist/provider-api/actions/custom-provider-registration.d.ts +12 -12
- package/dist/provider-api/actions/provider-api.d.ts +7 -7
- package/dist/resources/handlers.d.ts +1 -1
- package/dist/scripts/agent-engines/list-agent-engines.d.ts.map +1 -1
- package/dist/scripts/agent-engines/list-agent-engines.js +1 -18
- package/dist/scripts/agent-engines/list-agent-engines.js.map +1 -1
- package/dist/scripts/call-agent.d.ts.map +1 -1
- package/dist/scripts/call-agent.js +49 -6
- package/dist/scripts/call-agent.js.map +1 -1
- package/dist/secrets/routes.d.ts +3 -3
- package/dist/server/agent-chat-plugin.d.ts.map +1 -1
- package/dist/server/agent-chat-plugin.js +38 -10
- package/dist/server/agent-chat-plugin.js.map +1 -1
- package/dist/server/agent-engine-api-key-route.d.ts +1 -1
- package/dist/server/realtime-token.d.ts +1 -1
- package/dist/server/transcribe-voice.d.ts +1 -1
- package/dist/templates/workspace-core/.agents/skills/a2a-protocol/SKILL.md +16 -0
- package/docs/content/a2a-protocol.mdx +15 -0
- package/docs/content/template-chat.mdx +1 -1
- package/docs/content/template-plan.mdx +3 -3
- package/package.json +1 -1
- package/src/a2a/activity.ts +371 -0
- package/src/a2a/client.ts +3 -1
- package/src/a2a/index.ts +21 -0
- package/src/a2a/types.ts +8 -0
- package/src/agent/model-config.ts +19 -23
- package/src/agent/production-agent.ts +167 -100
- package/src/agent/types.ts +16 -1
- package/src/client/chat/tool-call-display.tsx +190 -35
- package/src/client/chat-model-groups.ts +67 -27
- package/src/client/sharing/ShareButton.tsx +1 -27
- package/src/client/sharing/ShareDialog.tsx +0 -16
- package/src/client/sharing/useShareButtonController.ts +0 -7
- package/src/client/sharing/useShareDialogController.ts +0 -2
- package/src/client/sse-event-processor.ts +85 -1
- package/src/ingestion/figma-node-to-html.ts +20 -99
- package/src/ingestion/figma-paint-math.ts +371 -0
- package/src/ingestion/index.ts +20 -0
- package/src/localization/default-messages.ts +6 -0
- package/src/scripts/agent-engines/list-agent-engines.ts +1 -18
- package/src/scripts/call-agent.ts +49 -6
- package/src/server/agent-chat-plugin.ts +50 -10
- package/src/templates/workspace-core/.agents/skills/a2a-protocol/SKILL.md +16 -0
- package/corpus/core/src/observability/hosted-model-experiment.ts +0 -118
- package/dist/observability/hosted-model-experiment.d.ts +0 -39
- package/dist/observability/hosted-model-experiment.d.ts.map +0 -1
- package/dist/observability/hosted-model-experiment.js +0 -90
- package/dist/observability/hosted-model-experiment.js.map +0 -1
- package/src/observability/hosted-model-experiment.ts +0 -118
|
@@ -13,6 +13,11 @@ import {
|
|
|
13
13
|
type H3Event,
|
|
14
14
|
} from "h3";
|
|
15
15
|
|
|
16
|
+
import {
|
|
17
|
+
applyA2AAgentActivityEvent,
|
|
18
|
+
buildA2AAgentActivityPart,
|
|
19
|
+
createA2AAgentActivityState,
|
|
20
|
+
} from "../a2a/activity.js";
|
|
16
21
|
import {
|
|
17
22
|
appendA2AArtifactLinks,
|
|
18
23
|
buildA2ARecoverableArtifactMessage,
|
|
@@ -400,6 +405,8 @@ export function createSerializedA2ATaskStatusWriter(
|
|
|
400
405
|
};
|
|
401
406
|
}
|
|
402
407
|
|
|
408
|
+
const A2A_ACTIVITY_CHECKPOINT_MIN_INTERVAL_MS = 2_000;
|
|
409
|
+
|
|
403
410
|
export async function resolveA2ARecoverableArtifactSecret(
|
|
404
411
|
orgId: string | null | undefined = getRequestOrgId(),
|
|
405
412
|
): Promise<string | undefined> {
|
|
@@ -1656,10 +1663,36 @@ export function createAgentChatPlugin(
|
|
|
1656
1663
|
const a2aEvents: AgentChatEvent[] = [];
|
|
1657
1664
|
const a2aToolResults: A2AToolResultSummary[] = [];
|
|
1658
1665
|
let lastRecoverableArtifactText = "";
|
|
1666
|
+
let activityState = createA2AAgentActivityState();
|
|
1667
|
+
let lastActivityCheckpointAt = 0;
|
|
1659
1668
|
const recoverableArtifactSecret =
|
|
1660
1669
|
await resolveA2ARecoverableArtifactSecret();
|
|
1661
1670
|
const recoverableArtifactStatusWriter =
|
|
1662
1671
|
createSerializedA2ATaskStatusWriter(context.taskId);
|
|
1672
|
+
const activityStatusMessage = (): A2AMessage => ({
|
|
1673
|
+
role: "agent",
|
|
1674
|
+
...(lastRecoverableArtifactText
|
|
1675
|
+
? { metadata: { agentNativeRecoverableArtifacts: true } }
|
|
1676
|
+
: {}),
|
|
1677
|
+
parts: [
|
|
1678
|
+
buildA2AAgentActivityPart(activityState),
|
|
1679
|
+
...(lastRecoverableArtifactText
|
|
1680
|
+
? [{ type: "text" as const, text: lastRecoverableArtifactText }]
|
|
1681
|
+
: []),
|
|
1682
|
+
],
|
|
1683
|
+
});
|
|
1684
|
+
const checkpointActivity = (force = false) => {
|
|
1685
|
+
const now = Date.now();
|
|
1686
|
+
if (
|
|
1687
|
+
!force &&
|
|
1688
|
+
now - lastActivityCheckpointAt <
|
|
1689
|
+
A2A_ACTIVITY_CHECKPOINT_MIN_INTERVAL_MS
|
|
1690
|
+
) {
|
|
1691
|
+
return;
|
|
1692
|
+
}
|
|
1693
|
+
lastActivityCheckpointAt = now;
|
|
1694
|
+
recoverableArtifactStatusWriter.enqueue(activityStatusMessage());
|
|
1695
|
+
};
|
|
1663
1696
|
const controller = new AbortController();
|
|
1664
1697
|
const correlation = sanitizeA2ACorrelationMetadata(context.metadata);
|
|
1665
1698
|
const telemetryThreadId =
|
|
@@ -1697,6 +1730,12 @@ export function createAgentChatPlugin(
|
|
|
1697
1730
|
turnId: context.taskId,
|
|
1698
1731
|
send: (event) => {
|
|
1699
1732
|
a2aEvents.push(event);
|
|
1733
|
+
const nextActivityState = applyA2AAgentActivityEvent(
|
|
1734
|
+
activityState,
|
|
1735
|
+
event,
|
|
1736
|
+
);
|
|
1737
|
+
const activityChanged = nextActivityState !== activityState;
|
|
1738
|
+
activityState = nextActivityState;
|
|
1700
1739
|
if (event.type === "tool_start") {
|
|
1701
1740
|
console.log(`[A2A] Tool call: ${event.tool}`);
|
|
1702
1741
|
} else if (event.type === "tool_done") {
|
|
@@ -1727,22 +1766,20 @@ export function createAgentChatPlugin(
|
|
|
1727
1766
|
recoverableArtifactText !== lastRecoverableArtifactText
|
|
1728
1767
|
) {
|
|
1729
1768
|
lastRecoverableArtifactText = recoverableArtifactText;
|
|
1730
|
-
recoverableArtifactStatusWriter.enqueue({
|
|
1731
|
-
role: "agent",
|
|
1732
|
-
metadata: { agentNativeRecoverableArtifacts: true },
|
|
1733
|
-
parts: [
|
|
1734
|
-
{
|
|
1735
|
-
type: "text",
|
|
1736
|
-
text: recoverableArtifactText,
|
|
1737
|
-
},
|
|
1738
|
-
],
|
|
1739
|
-
});
|
|
1740
1769
|
}
|
|
1741
1770
|
} else if (event.type === "error") {
|
|
1742
1771
|
console.error(`[A2A] Error: ${event.error}`);
|
|
1743
1772
|
} else if (event.type === "done") {
|
|
1744
1773
|
console.log(`[A2A] Done. Events: ${a2aEvents.length}`);
|
|
1745
1774
|
}
|
|
1775
|
+
if (activityChanged) {
|
|
1776
|
+
checkpointActivity(
|
|
1777
|
+
event.type === "tool_start" ||
|
|
1778
|
+
event.type === "tool_done" ||
|
|
1779
|
+
event.type === "error" ||
|
|
1780
|
+
event.type === "done",
|
|
1781
|
+
);
|
|
1782
|
+
}
|
|
1746
1783
|
},
|
|
1747
1784
|
signal: controller.signal,
|
|
1748
1785
|
},
|
|
@@ -1779,6 +1816,7 @@ export function createAgentChatPlugin(
|
|
|
1779
1816
|
|
|
1780
1817
|
// The continuation can observe terminal output immediately, so make
|
|
1781
1818
|
// its latest mutation checkpoint durable first.
|
|
1819
|
+
checkpointActivity(true);
|
|
1782
1820
|
await recoverableArtifactStatusWriter.flush();
|
|
1783
1821
|
|
|
1784
1822
|
const approval = [...a2aEvents]
|
|
@@ -1817,6 +1855,7 @@ export function createAgentChatPlugin(
|
|
|
1817
1855
|
},
|
|
1818
1856
|
},
|
|
1819
1857
|
parts: [
|
|
1858
|
+
buildA2AAgentActivityPart(activityState),
|
|
1820
1859
|
{
|
|
1821
1860
|
type: "text" as const,
|
|
1822
1861
|
text:
|
|
@@ -1851,6 +1890,7 @@ export function createAgentChatPlugin(
|
|
|
1851
1890
|
yield {
|
|
1852
1891
|
role: "agent" as const,
|
|
1853
1892
|
parts: [
|
|
1893
|
+
buildA2AAgentActivityPart(activityState),
|
|
1854
1894
|
{
|
|
1855
1895
|
type: "text" as const,
|
|
1856
1896
|
text: finalText || "(no response)",
|
|
@@ -195,6 +195,22 @@ for await (const update of client.stream({
|
|
|
195
195
|
}
|
|
196
196
|
```
|
|
197
197
|
|
|
198
|
+
### Agent activity in delegated chat
|
|
199
|
+
|
|
200
|
+
Agent Native peers attach a bounded `data` part with
|
|
201
|
+
`kind: "agent-native/agent-activity"` to in-progress and terminal task status
|
|
202
|
+
messages. It contains the same user-visible reasoning summaries shown in the
|
|
203
|
+
receiving app, tool names and completion states, elapsed time, and progressive
|
|
204
|
+
response text. It never includes tool inputs, tool results, credentials, or
|
|
205
|
+
hidden provider reasoning.
|
|
206
|
+
|
|
207
|
+
`call-agent` reads this optional part while it polls an asynchronous task and
|
|
208
|
+
renders the remote work as a nested agent run. Unknown A2A peers do not need to
|
|
209
|
+
implement the extension: their ordinary status and final text still render in
|
|
210
|
+
the same nested block. Treat activity data as untrusted presentation content;
|
|
211
|
+
never use it for identity, authorization, approval, routing, or artifact
|
|
212
|
+
validation.
|
|
213
|
+
|
|
198
214
|
### Carrying explicit chat authorization
|
|
199
215
|
|
|
200
216
|
When the authenticated caller has an exact consequential action that the user
|
|
@@ -1,118 +0,0 @@
|
|
|
1
|
-
export const HOSTED_DEFAULT_MODEL_EXPERIMENT_ID =
|
|
2
|
-
"hosted-default-model-sonnet-5-vs-gpt-5-6-luna-2026-07";
|
|
3
|
-
|
|
4
|
-
export const HOSTED_DEFAULT_MODEL_CONTROL = {
|
|
5
|
-
id: "sonnet-5",
|
|
6
|
-
model: "claude-sonnet-5",
|
|
7
|
-
weight: 80,
|
|
8
|
-
} as const;
|
|
9
|
-
|
|
10
|
-
export const HOSTED_DEFAULT_MODEL_TREATMENT = {
|
|
11
|
-
id: "gpt-5-6-luna",
|
|
12
|
-
model: "gpt-5-6-luna",
|
|
13
|
-
weight: 20,
|
|
14
|
-
} as const;
|
|
15
|
-
|
|
16
|
-
export interface HostedDefaultModelExperimentAssignment {
|
|
17
|
-
experimentId: string;
|
|
18
|
-
variantId: string;
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
export interface HostedDefaultModelExperimentResolution {
|
|
22
|
-
model: string;
|
|
23
|
-
assignment: HostedDefaultModelExperimentAssignment;
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
type HostedExperimentEnv = Record<string, string | undefined>;
|
|
27
|
-
|
|
28
|
-
const FIRST_PARTY_HOST_SUFFIX = ".agent-native.com";
|
|
29
|
-
const ENABLED_VALUES = new Set(["1", "true", "on", "yes"]);
|
|
30
|
-
const DISABLED_VALUES = new Set(["0", "false", "off", "no"]);
|
|
31
|
-
|
|
32
|
-
function hostnameFromUrl(value: string | undefined): string | null {
|
|
33
|
-
const trimmed = value?.trim();
|
|
34
|
-
if (!trimmed) return null;
|
|
35
|
-
try {
|
|
36
|
-
const url = /^[a-z][a-z0-9+.-]*:\/\//i.test(trimmed)
|
|
37
|
-
? new URL(trimmed)
|
|
38
|
-
: new URL(`https://${trimmed}`);
|
|
39
|
-
return url.hostname.toLowerCase();
|
|
40
|
-
} catch {
|
|
41
|
-
return null;
|
|
42
|
-
}
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
/**
|
|
46
|
-
* The rollout is automatic only on first-party hosted app domains. Operators
|
|
47
|
-
* can explicitly enable or disable it for preview and emergency rollback.
|
|
48
|
-
*/
|
|
49
|
-
export function isHostedDefaultModelExperimentEnabled(
|
|
50
|
-
env: HostedExperimentEnv = process.env,
|
|
51
|
-
): boolean {
|
|
52
|
-
const override =
|
|
53
|
-
env.AGENT_NATIVE_HOSTED_MODEL_EXPERIMENT?.trim().toLowerCase();
|
|
54
|
-
if (override && DISABLED_VALUES.has(override)) return false;
|
|
55
|
-
if (override && ENABLED_VALUES.has(override)) return true;
|
|
56
|
-
|
|
57
|
-
const hostnames = [
|
|
58
|
-
env.APP_URL,
|
|
59
|
-
env.BETTER_AUTH_URL,
|
|
60
|
-
env.URL,
|
|
61
|
-
env.DEPLOY_URL,
|
|
62
|
-
env.VERCEL_PROJECT_PRODUCTION_URL,
|
|
63
|
-
env.VERCEL_URL,
|
|
64
|
-
]
|
|
65
|
-
.map(hostnameFromUrl)
|
|
66
|
-
.filter((hostname): hostname is string => Boolean(hostname));
|
|
67
|
-
|
|
68
|
-
return hostnames.some(
|
|
69
|
-
(hostname) =>
|
|
70
|
-
hostname === "agent-native.com" ||
|
|
71
|
-
hostname.endsWith(FIRST_PARTY_HOST_SUFFIX),
|
|
72
|
-
);
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
/**
|
|
76
|
-
* Stable cross-app bucket: the same authenticated user receives the same
|
|
77
|
-
* variant in every hosted template for the life of this experiment id.
|
|
78
|
-
*/
|
|
79
|
-
export function hostedDefaultModelExperimentBucket(userId: string): number {
|
|
80
|
-
const input = `${HOSTED_DEFAULT_MODEL_EXPERIMENT_ID}:${userId.trim().toLowerCase()}`;
|
|
81
|
-
let hash = 0x811c9dc5;
|
|
82
|
-
for (let index = 0; index < input.length; index++) {
|
|
83
|
-
hash ^= input.charCodeAt(index);
|
|
84
|
-
hash = Math.imul(hash, 0x01000193);
|
|
85
|
-
}
|
|
86
|
-
return (hash >>> 0) % 100;
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
export function resolveHostedDefaultModelExperiment(args: {
|
|
90
|
-
userId: string | null | undefined;
|
|
91
|
-
engineName: string;
|
|
92
|
-
isDefaultModelSelection: boolean;
|
|
93
|
-
supportedModels?: readonly string[];
|
|
94
|
-
env?: HostedExperimentEnv;
|
|
95
|
-
}): HostedDefaultModelExperimentResolution | null {
|
|
96
|
-
const userId = args.userId?.trim();
|
|
97
|
-
if (!userId) return null;
|
|
98
|
-
if (args.engineName !== "builder") return null;
|
|
99
|
-
if (!args.isDefaultModelSelection) return null;
|
|
100
|
-
if (!isHostedDefaultModelExperimentEnabled(args.env)) return null;
|
|
101
|
-
|
|
102
|
-
const variant =
|
|
103
|
-
hostedDefaultModelExperimentBucket(userId) <
|
|
104
|
-
HOSTED_DEFAULT_MODEL_TREATMENT.weight
|
|
105
|
-
? HOSTED_DEFAULT_MODEL_TREATMENT
|
|
106
|
-
: HOSTED_DEFAULT_MODEL_CONTROL;
|
|
107
|
-
if (args.supportedModels && !args.supportedModels.includes(variant.model)) {
|
|
108
|
-
return null;
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
return {
|
|
112
|
-
model: variant.model,
|
|
113
|
-
assignment: {
|
|
114
|
-
experimentId: HOSTED_DEFAULT_MODEL_EXPERIMENT_ID,
|
|
115
|
-
variantId: variant.id,
|
|
116
|
-
},
|
|
117
|
-
};
|
|
118
|
-
}
|
|
@@ -1,39 +0,0 @@
|
|
|
1
|
-
export declare const HOSTED_DEFAULT_MODEL_EXPERIMENT_ID = "hosted-default-model-sonnet-5-vs-gpt-5-6-luna-2026-07";
|
|
2
|
-
export declare const HOSTED_DEFAULT_MODEL_CONTROL: {
|
|
3
|
-
readonly id: "sonnet-5";
|
|
4
|
-
readonly model: "claude-sonnet-5";
|
|
5
|
-
readonly weight: 80;
|
|
6
|
-
};
|
|
7
|
-
export declare const HOSTED_DEFAULT_MODEL_TREATMENT: {
|
|
8
|
-
readonly id: "gpt-5-6-luna";
|
|
9
|
-
readonly model: "gpt-5-6-luna";
|
|
10
|
-
readonly weight: 20;
|
|
11
|
-
};
|
|
12
|
-
export interface HostedDefaultModelExperimentAssignment {
|
|
13
|
-
experimentId: string;
|
|
14
|
-
variantId: string;
|
|
15
|
-
}
|
|
16
|
-
export interface HostedDefaultModelExperimentResolution {
|
|
17
|
-
model: string;
|
|
18
|
-
assignment: HostedDefaultModelExperimentAssignment;
|
|
19
|
-
}
|
|
20
|
-
type HostedExperimentEnv = Record<string, string | undefined>;
|
|
21
|
-
/**
|
|
22
|
-
* The rollout is automatic only on first-party hosted app domains. Operators
|
|
23
|
-
* can explicitly enable or disable it for preview and emergency rollback.
|
|
24
|
-
*/
|
|
25
|
-
export declare function isHostedDefaultModelExperimentEnabled(env?: HostedExperimentEnv): boolean;
|
|
26
|
-
/**
|
|
27
|
-
* Stable cross-app bucket: the same authenticated user receives the same
|
|
28
|
-
* variant in every hosted template for the life of this experiment id.
|
|
29
|
-
*/
|
|
30
|
-
export declare function hostedDefaultModelExperimentBucket(userId: string): number;
|
|
31
|
-
export declare function resolveHostedDefaultModelExperiment(args: {
|
|
32
|
-
userId: string | null | undefined;
|
|
33
|
-
engineName: string;
|
|
34
|
-
isDefaultModelSelection: boolean;
|
|
35
|
-
supportedModels?: readonly string[];
|
|
36
|
-
env?: HostedExperimentEnv;
|
|
37
|
-
}): HostedDefaultModelExperimentResolution | null;
|
|
38
|
-
export {};
|
|
39
|
-
//# sourceMappingURL=hosted-model-experiment.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"hosted-model-experiment.d.ts","sourceRoot":"","sources":["../../src/observability/hosted-model-experiment.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,kCAAkC,0DACU,CAAC;AAE1D,eAAO,MAAM,4BAA4B;aACvC,EAAE,EAAE,UAAU;aACd,KAAK,EAAE,iBAAiB;aACxB,MAAM,EAAE,EAAE;CACF,CAAC;AAEX,eAAO,MAAM,8BAA8B;aACzC,EAAE,EAAE,cAAc;aAClB,KAAK,EAAE,cAAc;aACrB,MAAM,EAAE,EAAE;CACF,CAAC;AAEX,MAAM,WAAW,sCAAsC;IACrD,YAAY,EAAE,MAAM,CAAC;IACrB,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,sCAAsC;IACrD,KAAK,EAAE,MAAM,CAAC;IACd,UAAU,EAAE,sCAAsC,CAAC;CACpD;AAED,KAAK,mBAAmB,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC,CAAC;AAmB9D;;;GAGG;AACH,wBAAgB,qCAAqC,CACnD,GAAG,GAAE,mBAAiC,GACrC,OAAO,CAsBT;AAED;;;GAGG;AACH,wBAAgB,kCAAkC,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAQzE;AAED,wBAAgB,mCAAmC,CAAC,IAAI,EAAE;IACxD,MAAM,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,CAAC;IAClC,UAAU,EAAE,MAAM,CAAC;IACnB,uBAAuB,EAAE,OAAO,CAAC;IACjC,eAAe,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IACpC,GAAG,CAAC,EAAE,mBAAmB,CAAC;CAC3B,GAAG,sCAAsC,GAAG,IAAI,CAuBhD"}
|
|
@@ -1,90 +0,0 @@
|
|
|
1
|
-
export const HOSTED_DEFAULT_MODEL_EXPERIMENT_ID = "hosted-default-model-sonnet-5-vs-gpt-5-6-luna-2026-07";
|
|
2
|
-
export const HOSTED_DEFAULT_MODEL_CONTROL = {
|
|
3
|
-
id: "sonnet-5",
|
|
4
|
-
model: "claude-sonnet-5",
|
|
5
|
-
weight: 80,
|
|
6
|
-
};
|
|
7
|
-
export const HOSTED_DEFAULT_MODEL_TREATMENT = {
|
|
8
|
-
id: "gpt-5-6-luna",
|
|
9
|
-
model: "gpt-5-6-luna",
|
|
10
|
-
weight: 20,
|
|
11
|
-
};
|
|
12
|
-
const FIRST_PARTY_HOST_SUFFIX = ".agent-native.com";
|
|
13
|
-
const ENABLED_VALUES = new Set(["1", "true", "on", "yes"]);
|
|
14
|
-
const DISABLED_VALUES = new Set(["0", "false", "off", "no"]);
|
|
15
|
-
function hostnameFromUrl(value) {
|
|
16
|
-
const trimmed = value?.trim();
|
|
17
|
-
if (!trimmed)
|
|
18
|
-
return null;
|
|
19
|
-
try {
|
|
20
|
-
const url = /^[a-z][a-z0-9+.-]*:\/\//i.test(trimmed)
|
|
21
|
-
? new URL(trimmed)
|
|
22
|
-
: new URL(`https://${trimmed}`);
|
|
23
|
-
return url.hostname.toLowerCase();
|
|
24
|
-
}
|
|
25
|
-
catch {
|
|
26
|
-
return null;
|
|
27
|
-
}
|
|
28
|
-
}
|
|
29
|
-
/**
|
|
30
|
-
* The rollout is automatic only on first-party hosted app domains. Operators
|
|
31
|
-
* can explicitly enable or disable it for preview and emergency rollback.
|
|
32
|
-
*/
|
|
33
|
-
export function isHostedDefaultModelExperimentEnabled(env = process.env) {
|
|
34
|
-
const override = env.AGENT_NATIVE_HOSTED_MODEL_EXPERIMENT?.trim().toLowerCase();
|
|
35
|
-
if (override && DISABLED_VALUES.has(override))
|
|
36
|
-
return false;
|
|
37
|
-
if (override && ENABLED_VALUES.has(override))
|
|
38
|
-
return true;
|
|
39
|
-
const hostnames = [
|
|
40
|
-
env.APP_URL,
|
|
41
|
-
env.BETTER_AUTH_URL,
|
|
42
|
-
env.URL,
|
|
43
|
-
env.DEPLOY_URL,
|
|
44
|
-
env.VERCEL_PROJECT_PRODUCTION_URL,
|
|
45
|
-
env.VERCEL_URL,
|
|
46
|
-
]
|
|
47
|
-
.map(hostnameFromUrl)
|
|
48
|
-
.filter((hostname) => Boolean(hostname));
|
|
49
|
-
return hostnames.some((hostname) => hostname === "agent-native.com" ||
|
|
50
|
-
hostname.endsWith(FIRST_PARTY_HOST_SUFFIX));
|
|
51
|
-
}
|
|
52
|
-
/**
|
|
53
|
-
* Stable cross-app bucket: the same authenticated user receives the same
|
|
54
|
-
* variant in every hosted template for the life of this experiment id.
|
|
55
|
-
*/
|
|
56
|
-
export function hostedDefaultModelExperimentBucket(userId) {
|
|
57
|
-
const input = `${HOSTED_DEFAULT_MODEL_EXPERIMENT_ID}:${userId.trim().toLowerCase()}`;
|
|
58
|
-
let hash = 0x811c9dc5;
|
|
59
|
-
for (let index = 0; index < input.length; index++) {
|
|
60
|
-
hash ^= input.charCodeAt(index);
|
|
61
|
-
hash = Math.imul(hash, 0x01000193);
|
|
62
|
-
}
|
|
63
|
-
return (hash >>> 0) % 100;
|
|
64
|
-
}
|
|
65
|
-
export function resolveHostedDefaultModelExperiment(args) {
|
|
66
|
-
const userId = args.userId?.trim();
|
|
67
|
-
if (!userId)
|
|
68
|
-
return null;
|
|
69
|
-
if (args.engineName !== "builder")
|
|
70
|
-
return null;
|
|
71
|
-
if (!args.isDefaultModelSelection)
|
|
72
|
-
return null;
|
|
73
|
-
if (!isHostedDefaultModelExperimentEnabled(args.env))
|
|
74
|
-
return null;
|
|
75
|
-
const variant = hostedDefaultModelExperimentBucket(userId) <
|
|
76
|
-
HOSTED_DEFAULT_MODEL_TREATMENT.weight
|
|
77
|
-
? HOSTED_DEFAULT_MODEL_TREATMENT
|
|
78
|
-
: HOSTED_DEFAULT_MODEL_CONTROL;
|
|
79
|
-
if (args.supportedModels && !args.supportedModels.includes(variant.model)) {
|
|
80
|
-
return null;
|
|
81
|
-
}
|
|
82
|
-
return {
|
|
83
|
-
model: variant.model,
|
|
84
|
-
assignment: {
|
|
85
|
-
experimentId: HOSTED_DEFAULT_MODEL_EXPERIMENT_ID,
|
|
86
|
-
variantId: variant.id,
|
|
87
|
-
},
|
|
88
|
-
};
|
|
89
|
-
}
|
|
90
|
-
//# sourceMappingURL=hosted-model-experiment.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"hosted-model-experiment.js","sourceRoot":"","sources":["../../src/observability/hosted-model-experiment.ts"],"names":[],"mappings":"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","sourcesContent":["export const HOSTED_DEFAULT_MODEL_EXPERIMENT_ID =\n \"hosted-default-model-sonnet-5-vs-gpt-5-6-luna-2026-07\";\n\nexport const HOSTED_DEFAULT_MODEL_CONTROL = {\n id: \"sonnet-5\",\n model: \"claude-sonnet-5\",\n weight: 80,\n} as const;\n\nexport const HOSTED_DEFAULT_MODEL_TREATMENT = {\n id: \"gpt-5-6-luna\",\n model: \"gpt-5-6-luna\",\n weight: 20,\n} as const;\n\nexport interface HostedDefaultModelExperimentAssignment {\n experimentId: string;\n variantId: string;\n}\n\nexport interface HostedDefaultModelExperimentResolution {\n model: string;\n assignment: HostedDefaultModelExperimentAssignment;\n}\n\ntype HostedExperimentEnv = Record<string, string | undefined>;\n\nconst FIRST_PARTY_HOST_SUFFIX = \".agent-native.com\";\nconst ENABLED_VALUES = new Set([\"1\", \"true\", \"on\", \"yes\"]);\nconst DISABLED_VALUES = new Set([\"0\", \"false\", \"off\", \"no\"]);\n\nfunction hostnameFromUrl(value: string | undefined): string | null {\n const trimmed = value?.trim();\n if (!trimmed) return null;\n try {\n const url = /^[a-z][a-z0-9+.-]*:\\/\\//i.test(trimmed)\n ? new URL(trimmed)\n : new URL(`https://${trimmed}`);\n return url.hostname.toLowerCase();\n } catch {\n return null;\n }\n}\n\n/**\n * The rollout is automatic only on first-party hosted app domains. Operators\n * can explicitly enable or disable it for preview and emergency rollback.\n */\nexport function isHostedDefaultModelExperimentEnabled(\n env: HostedExperimentEnv = process.env,\n): boolean {\n const override =\n env.AGENT_NATIVE_HOSTED_MODEL_EXPERIMENT?.trim().toLowerCase();\n if (override && DISABLED_VALUES.has(override)) return false;\n if (override && ENABLED_VALUES.has(override)) return true;\n\n const hostnames = [\n env.APP_URL,\n env.BETTER_AUTH_URL,\n env.URL,\n env.DEPLOY_URL,\n env.VERCEL_PROJECT_PRODUCTION_URL,\n env.VERCEL_URL,\n ]\n .map(hostnameFromUrl)\n .filter((hostname): hostname is string => Boolean(hostname));\n\n return hostnames.some(\n (hostname) =>\n hostname === \"agent-native.com\" ||\n hostname.endsWith(FIRST_PARTY_HOST_SUFFIX),\n );\n}\n\n/**\n * Stable cross-app bucket: the same authenticated user receives the same\n * variant in every hosted template for the life of this experiment id.\n */\nexport function hostedDefaultModelExperimentBucket(userId: string): number {\n const input = `${HOSTED_DEFAULT_MODEL_EXPERIMENT_ID}:${userId.trim().toLowerCase()}`;\n let hash = 0x811c9dc5;\n for (let index = 0; index < input.length; index++) {\n hash ^= input.charCodeAt(index);\n hash = Math.imul(hash, 0x01000193);\n }\n return (hash >>> 0) % 100;\n}\n\nexport function resolveHostedDefaultModelExperiment(args: {\n userId: string | null | undefined;\n engineName: string;\n isDefaultModelSelection: boolean;\n supportedModels?: readonly string[];\n env?: HostedExperimentEnv;\n}): HostedDefaultModelExperimentResolution | null {\n const userId = args.userId?.trim();\n if (!userId) return null;\n if (args.engineName !== \"builder\") return null;\n if (!args.isDefaultModelSelection) return null;\n if (!isHostedDefaultModelExperimentEnabled(args.env)) return null;\n\n const variant =\n hostedDefaultModelExperimentBucket(userId) <\n HOSTED_DEFAULT_MODEL_TREATMENT.weight\n ? HOSTED_DEFAULT_MODEL_TREATMENT\n : HOSTED_DEFAULT_MODEL_CONTROL;\n if (args.supportedModels && !args.supportedModels.includes(variant.model)) {\n return null;\n }\n\n return {\n model: variant.model,\n assignment: {\n experimentId: HOSTED_DEFAULT_MODEL_EXPERIMENT_ID,\n variantId: variant.id,\n },\n };\n}\n"]}
|
|
@@ -1,118 +0,0 @@
|
|
|
1
|
-
export const HOSTED_DEFAULT_MODEL_EXPERIMENT_ID =
|
|
2
|
-
"hosted-default-model-sonnet-5-vs-gpt-5-6-luna-2026-07";
|
|
3
|
-
|
|
4
|
-
export const HOSTED_DEFAULT_MODEL_CONTROL = {
|
|
5
|
-
id: "sonnet-5",
|
|
6
|
-
model: "claude-sonnet-5",
|
|
7
|
-
weight: 80,
|
|
8
|
-
} as const;
|
|
9
|
-
|
|
10
|
-
export const HOSTED_DEFAULT_MODEL_TREATMENT = {
|
|
11
|
-
id: "gpt-5-6-luna",
|
|
12
|
-
model: "gpt-5-6-luna",
|
|
13
|
-
weight: 20,
|
|
14
|
-
} as const;
|
|
15
|
-
|
|
16
|
-
export interface HostedDefaultModelExperimentAssignment {
|
|
17
|
-
experimentId: string;
|
|
18
|
-
variantId: string;
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
export interface HostedDefaultModelExperimentResolution {
|
|
22
|
-
model: string;
|
|
23
|
-
assignment: HostedDefaultModelExperimentAssignment;
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
type HostedExperimentEnv = Record<string, string | undefined>;
|
|
27
|
-
|
|
28
|
-
const FIRST_PARTY_HOST_SUFFIX = ".agent-native.com";
|
|
29
|
-
const ENABLED_VALUES = new Set(["1", "true", "on", "yes"]);
|
|
30
|
-
const DISABLED_VALUES = new Set(["0", "false", "off", "no"]);
|
|
31
|
-
|
|
32
|
-
function hostnameFromUrl(value: string | undefined): string | null {
|
|
33
|
-
const trimmed = value?.trim();
|
|
34
|
-
if (!trimmed) return null;
|
|
35
|
-
try {
|
|
36
|
-
const url = /^[a-z][a-z0-9+.-]*:\/\//i.test(trimmed)
|
|
37
|
-
? new URL(trimmed)
|
|
38
|
-
: new URL(`https://${trimmed}`);
|
|
39
|
-
return url.hostname.toLowerCase();
|
|
40
|
-
} catch {
|
|
41
|
-
return null;
|
|
42
|
-
}
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
/**
|
|
46
|
-
* The rollout is automatic only on first-party hosted app domains. Operators
|
|
47
|
-
* can explicitly enable or disable it for preview and emergency rollback.
|
|
48
|
-
*/
|
|
49
|
-
export function isHostedDefaultModelExperimentEnabled(
|
|
50
|
-
env: HostedExperimentEnv = process.env,
|
|
51
|
-
): boolean {
|
|
52
|
-
const override =
|
|
53
|
-
env.AGENT_NATIVE_HOSTED_MODEL_EXPERIMENT?.trim().toLowerCase();
|
|
54
|
-
if (override && DISABLED_VALUES.has(override)) return false;
|
|
55
|
-
if (override && ENABLED_VALUES.has(override)) return true;
|
|
56
|
-
|
|
57
|
-
const hostnames = [
|
|
58
|
-
env.APP_URL,
|
|
59
|
-
env.BETTER_AUTH_URL,
|
|
60
|
-
env.URL,
|
|
61
|
-
env.DEPLOY_URL,
|
|
62
|
-
env.VERCEL_PROJECT_PRODUCTION_URL,
|
|
63
|
-
env.VERCEL_URL,
|
|
64
|
-
]
|
|
65
|
-
.map(hostnameFromUrl)
|
|
66
|
-
.filter((hostname): hostname is string => Boolean(hostname));
|
|
67
|
-
|
|
68
|
-
return hostnames.some(
|
|
69
|
-
(hostname) =>
|
|
70
|
-
hostname === "agent-native.com" ||
|
|
71
|
-
hostname.endsWith(FIRST_PARTY_HOST_SUFFIX),
|
|
72
|
-
);
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
/**
|
|
76
|
-
* Stable cross-app bucket: the same authenticated user receives the same
|
|
77
|
-
* variant in every hosted template for the life of this experiment id.
|
|
78
|
-
*/
|
|
79
|
-
export function hostedDefaultModelExperimentBucket(userId: string): number {
|
|
80
|
-
const input = `${HOSTED_DEFAULT_MODEL_EXPERIMENT_ID}:${userId.trim().toLowerCase()}`;
|
|
81
|
-
let hash = 0x811c9dc5;
|
|
82
|
-
for (let index = 0; index < input.length; index++) {
|
|
83
|
-
hash ^= input.charCodeAt(index);
|
|
84
|
-
hash = Math.imul(hash, 0x01000193);
|
|
85
|
-
}
|
|
86
|
-
return (hash >>> 0) % 100;
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
export function resolveHostedDefaultModelExperiment(args: {
|
|
90
|
-
userId: string | null | undefined;
|
|
91
|
-
engineName: string;
|
|
92
|
-
isDefaultModelSelection: boolean;
|
|
93
|
-
supportedModels?: readonly string[];
|
|
94
|
-
env?: HostedExperimentEnv;
|
|
95
|
-
}): HostedDefaultModelExperimentResolution | null {
|
|
96
|
-
const userId = args.userId?.trim();
|
|
97
|
-
if (!userId) return null;
|
|
98
|
-
if (args.engineName !== "builder") return null;
|
|
99
|
-
if (!args.isDefaultModelSelection) return null;
|
|
100
|
-
if (!isHostedDefaultModelExperimentEnabled(args.env)) return null;
|
|
101
|
-
|
|
102
|
-
const variant =
|
|
103
|
-
hostedDefaultModelExperimentBucket(userId) <
|
|
104
|
-
HOSTED_DEFAULT_MODEL_TREATMENT.weight
|
|
105
|
-
? HOSTED_DEFAULT_MODEL_TREATMENT
|
|
106
|
-
: HOSTED_DEFAULT_MODEL_CONTROL;
|
|
107
|
-
if (args.supportedModels && !args.supportedModels.includes(variant.model)) {
|
|
108
|
-
return null;
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
return {
|
|
112
|
-
model: variant.model,
|
|
113
|
-
assignment: {
|
|
114
|
-
experimentId: HOSTED_DEFAULT_MODEL_EXPERIMENT_ID,
|
|
115
|
-
variantId: variant.id,
|
|
116
|
-
},
|
|
117
|
-
};
|
|
118
|
-
}
|