@frockbot/configuration-core 0.3.8 → 0.3.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +3 -3
- package/src/index.test.ts +139 -0
- package/src/index.ts +63 -7
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@frockbot/configuration-core",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.10",
|
|
4
4
|
"private": false,
|
|
5
5
|
"type": "module",
|
|
6
6
|
"exports": {
|
|
@@ -12,8 +12,8 @@
|
|
|
12
12
|
"typecheck": "tsc --noEmit -p tsconfig.json"
|
|
13
13
|
},
|
|
14
14
|
"dependencies": {
|
|
15
|
-
"@frockbot/connection-core": "0.3.
|
|
16
|
-
"@frockbot/kernel-composition": "0.3.
|
|
15
|
+
"@frockbot/connection-core": "0.3.10",
|
|
16
|
+
"@frockbot/kernel-composition": "0.3.10"
|
|
17
17
|
},
|
|
18
18
|
"devDependencies": {
|
|
19
19
|
"@types/bun": "1.3.6",
|
package/src/index.test.ts
CHANGED
|
@@ -26,6 +26,7 @@ import {
|
|
|
26
26
|
resolveEffectiveBotModelV1,
|
|
27
27
|
type ExecutionPackageDefinition,
|
|
28
28
|
type ModelBindingV1,
|
|
29
|
+
type UserSettingsViewV1,
|
|
29
30
|
} from "./index.js";
|
|
30
31
|
import {
|
|
31
32
|
isApplicationDeploymentHash,
|
|
@@ -1657,6 +1658,144 @@ describe("effective Bot model resolution", () => {
|
|
|
1657
1658
|
expect(effective.model).toBeUndefined();
|
|
1658
1659
|
});
|
|
1659
1660
|
|
|
1661
|
+
/**
|
|
1662
|
+
* The real account shape: a platform provider that is always there, and a
|
|
1663
|
+
* separate provider Package the User switched on and bound their model to.
|
|
1664
|
+
*/
|
|
1665
|
+
const flockModel: ModelBindingV1 = {
|
|
1666
|
+
connectionId: "flock-ai",
|
|
1667
|
+
providerModelId: "@flock/auto",
|
|
1668
|
+
};
|
|
1669
|
+
const flockPackage: ExecutionPackageDefinition = {
|
|
1670
|
+
packageId: "provider-flock-ai",
|
|
1671
|
+
version: "0.0.1",
|
|
1672
|
+
settings: [],
|
|
1673
|
+
capabilities: [
|
|
1674
|
+
{
|
|
1675
|
+
id: "flock-ai-models",
|
|
1676
|
+
kind: "model",
|
|
1677
|
+
connectionTypes: ["flock-ai-account"],
|
|
1678
|
+
},
|
|
1679
|
+
],
|
|
1680
|
+
connectionTypes: [
|
|
1681
|
+
{ id: "flock-ai-account", capabilities: ["flock-ai-models"] },
|
|
1682
|
+
],
|
|
1683
|
+
};
|
|
1684
|
+
function withFlock(): UserSettingsViewV1 {
|
|
1685
|
+
const base = user();
|
|
1686
|
+
return {
|
|
1687
|
+
...base,
|
|
1688
|
+
packages: [
|
|
1689
|
+
...base.packages,
|
|
1690
|
+
{
|
|
1691
|
+
packageId: "provider-flock-ai",
|
|
1692
|
+
version: "0.0.1",
|
|
1693
|
+
state: "installed",
|
|
1694
|
+
},
|
|
1695
|
+
{
|
|
1696
|
+
packageId: "custom-models",
|
|
1697
|
+
version: "0.0.1",
|
|
1698
|
+
state: "installed",
|
|
1699
|
+
values: { model: accountModel },
|
|
1700
|
+
},
|
|
1701
|
+
],
|
|
1702
|
+
connections: [
|
|
1703
|
+
...base.connections,
|
|
1704
|
+
{
|
|
1705
|
+
connectionId: "flock-ai",
|
|
1706
|
+
packageId: "provider-flock-ai",
|
|
1707
|
+
connectionTypeId: "flock-ai-account",
|
|
1708
|
+
displayName: "Flock AI",
|
|
1709
|
+
state: "ready",
|
|
1710
|
+
providerType: "flock-ai",
|
|
1711
|
+
modelCatalog: {
|
|
1712
|
+
schemaVersion: 1,
|
|
1713
|
+
generation: "flock-catalog-1",
|
|
1714
|
+
state: "fresh",
|
|
1715
|
+
models: [
|
|
1716
|
+
{
|
|
1717
|
+
providerModelId: "@flock/auto",
|
|
1718
|
+
displayName: "Auto",
|
|
1719
|
+
capabilities: { tools: true, vision: false, reasoning: true },
|
|
1720
|
+
source: "discovered",
|
|
1721
|
+
},
|
|
1722
|
+
],
|
|
1723
|
+
},
|
|
1724
|
+
safeMetadata: {},
|
|
1725
|
+
},
|
|
1726
|
+
],
|
|
1727
|
+
platformModel: flockModel,
|
|
1728
|
+
};
|
|
1729
|
+
}
|
|
1730
|
+
const flockPackages = [...modelPackages, flockPackage];
|
|
1731
|
+
|
|
1732
|
+
test("falls back to the platform model when the bound provider is switched off", () => {
|
|
1733
|
+
const providerOff = (() => {
|
|
1734
|
+
const settings = withFlock();
|
|
1735
|
+
return {
|
|
1736
|
+
...settings,
|
|
1737
|
+
packages: settings.packages.map((pkg) =>
|
|
1738
|
+
pkg.packageId === "provider-ollama-cloud"
|
|
1739
|
+
? { ...pkg, state: "disabled" as const }
|
|
1740
|
+
: pkg,
|
|
1741
|
+
),
|
|
1742
|
+
};
|
|
1743
|
+
})();
|
|
1744
|
+
const effective = resolveEffectiveBotModelV1({
|
|
1745
|
+
bot: { packageValues: {} },
|
|
1746
|
+
user: providerOff,
|
|
1747
|
+
packages: flockPackages,
|
|
1748
|
+
});
|
|
1749
|
+
expect(effective.source).toBe("platform");
|
|
1750
|
+
expect(effective.model).toEqual(flockModel);
|
|
1751
|
+
expect(effective.binding?.state).toBe("ready");
|
|
1752
|
+
expect(effective.fallback).toMatchObject({
|
|
1753
|
+
from: "account",
|
|
1754
|
+
model: accountModel,
|
|
1755
|
+
});
|
|
1756
|
+
});
|
|
1757
|
+
|
|
1758
|
+
test("falls back when a Bot's model has lost its Connection", () => {
|
|
1759
|
+
const settings = withFlock();
|
|
1760
|
+
const connectionGone: UserSettingsViewV1 = {
|
|
1761
|
+
...settings,
|
|
1762
|
+
connections: settings.connections.filter(
|
|
1763
|
+
(connection) => connection.connectionId !== "ollama-work",
|
|
1764
|
+
),
|
|
1765
|
+
};
|
|
1766
|
+
const effective = resolveEffectiveBotModelV1({
|
|
1767
|
+
bot: { packageValues: { "custom-models": { model: accountModel } } },
|
|
1768
|
+
user: connectionGone,
|
|
1769
|
+
packages: flockPackages,
|
|
1770
|
+
});
|
|
1771
|
+
expect(effective).toMatchObject({ source: "platform", model: flockModel });
|
|
1772
|
+
expect(effective.fallback?.from).toBe("bot");
|
|
1773
|
+
expect(effective.binding?.state).toBe("ready");
|
|
1774
|
+
});
|
|
1775
|
+
|
|
1776
|
+
test("keeps the failure when the platform model cannot stand in either", () => {
|
|
1777
|
+
// Nothing to degrade to: the platform bootstrap is bound to the same
|
|
1778
|
+
// switched-off Package, so the User still gets the repair sentence.
|
|
1779
|
+
const settings = withFlock();
|
|
1780
|
+
const bothOff: UserSettingsViewV1 = {
|
|
1781
|
+
...settings,
|
|
1782
|
+
packages: settings.packages.map((pkg) =>
|
|
1783
|
+
pkg.packageId === "provider-ollama-cloud"
|
|
1784
|
+
? { ...pkg, state: "disabled" as const }
|
|
1785
|
+
: pkg,
|
|
1786
|
+
),
|
|
1787
|
+
platformModel,
|
|
1788
|
+
};
|
|
1789
|
+
const effective = resolveEffectiveBotModelV1({
|
|
1790
|
+
bot: { packageValues: {} },
|
|
1791
|
+
user: bothOff,
|
|
1792
|
+
packages: flockPackages,
|
|
1793
|
+
});
|
|
1794
|
+
expect(effective.source).toBe("account");
|
|
1795
|
+
expect(effective.binding?.state).toBe("unavailable");
|
|
1796
|
+
expect(effective.fallback).toBeUndefined();
|
|
1797
|
+
});
|
|
1798
|
+
|
|
1660
1799
|
test("reports none only when no Package or platform model supplies one", () => {
|
|
1661
1800
|
const withoutPlatform = user();
|
|
1662
1801
|
delete withoutPlatform.platformModel;
|
package/src/index.ts
CHANGED
|
@@ -604,6 +604,17 @@ export interface EffectiveBotModelV1 {
|
|
|
604
604
|
source: "bot" | "account" | "platform" | "none";
|
|
605
605
|
model?: ModelBindingV1;
|
|
606
606
|
binding?: ResolvedModelBindingV1;
|
|
607
|
+
/**
|
|
608
|
+
* Set when a Bot or account choice was present but could not bind — its
|
|
609
|
+
* provider Package was switched off, or its Connection is gone — and the
|
|
610
|
+
* platform bootstrap answered in its place. The chosen scope and the reason
|
|
611
|
+
* it failed survive so the client can say the Bot is running on the default.
|
|
612
|
+
*/
|
|
613
|
+
fallback?: {
|
|
614
|
+
from: "bot" | "account";
|
|
615
|
+
model: ModelBindingV1;
|
|
616
|
+
failure: string;
|
|
617
|
+
};
|
|
607
618
|
}
|
|
608
619
|
|
|
609
620
|
/**
|
|
@@ -684,23 +695,68 @@ export function resolveEffectiveBotModelV1(input: {
|
|
|
684
695
|
};
|
|
685
696
|
};
|
|
686
697
|
|
|
698
|
+
/**
|
|
699
|
+
* A chosen model whose provider Package has been switched off, or whose
|
|
700
|
+
* Connection is gone, must not stop the Bot answering: the platform
|
|
701
|
+
* bootstrap stands in for it. Only a binding failure degrades this way — a
|
|
702
|
+
* scope conflict is the User's own contradiction and still fails closed,
|
|
703
|
+
* because there is no single choice to stand in for.
|
|
704
|
+
*/
|
|
705
|
+
const platformStandIn = (
|
|
706
|
+
from: "bot" | "account",
|
|
707
|
+
model: ModelBindingV1,
|
|
708
|
+
binding: ResolvedModelBindingV1,
|
|
709
|
+
): EffectiveBotModelV1 | undefined => {
|
|
710
|
+
const platform = input.user.platformModel;
|
|
711
|
+
if (!platform) return undefined;
|
|
712
|
+
if (
|
|
713
|
+
platform.connectionId === model.connectionId &&
|
|
714
|
+
platform.providerModelId === model.providerModelId
|
|
715
|
+
) {
|
|
716
|
+
return undefined;
|
|
717
|
+
}
|
|
718
|
+
const platformBinding = resolveBotModelBindingV1({
|
|
719
|
+
model: platform,
|
|
720
|
+
user: input.user,
|
|
721
|
+
packages: input.packages,
|
|
722
|
+
});
|
|
723
|
+
if (platformBinding.state === "unavailable") return undefined;
|
|
724
|
+
return {
|
|
725
|
+
source: "platform",
|
|
726
|
+
model: structuredClone(platform),
|
|
727
|
+
binding: platformBinding,
|
|
728
|
+
fallback: {
|
|
729
|
+
from,
|
|
730
|
+
model: structuredClone(model),
|
|
731
|
+
failure:
|
|
732
|
+
binding.failure ?? "This Bot's model isn't available right now.",
|
|
733
|
+
},
|
|
734
|
+
};
|
|
735
|
+
};
|
|
736
|
+
|
|
687
737
|
for (const scope of ["bot", "user"] as const) {
|
|
688
738
|
const resolved = fromScope(scope);
|
|
739
|
+
const source = scope === "bot" ? "bot" : "account";
|
|
689
740
|
if (resolved?.conflict) {
|
|
690
741
|
return {
|
|
691
|
-
source
|
|
742
|
+
source,
|
|
692
743
|
binding: { state: "unavailable", failure: resolved.conflict },
|
|
693
744
|
};
|
|
694
745
|
}
|
|
695
746
|
if (resolved?.model) {
|
|
747
|
+
const binding = resolveBotModelBindingV1({
|
|
748
|
+
model: resolved.model,
|
|
749
|
+
user: input.user,
|
|
750
|
+
packages: input.packages,
|
|
751
|
+
});
|
|
752
|
+
if (binding.state === "unavailable") {
|
|
753
|
+
const standIn = platformStandIn(source, resolved.model, binding);
|
|
754
|
+
if (standIn) return standIn;
|
|
755
|
+
}
|
|
696
756
|
return {
|
|
697
|
-
source
|
|
757
|
+
source,
|
|
698
758
|
model: structuredClone(resolved.model),
|
|
699
|
-
binding
|
|
700
|
-
model: resolved.model,
|
|
701
|
-
user: input.user,
|
|
702
|
-
packages: input.packages,
|
|
703
|
-
}),
|
|
759
|
+
binding,
|
|
704
760
|
};
|
|
705
761
|
}
|
|
706
762
|
}
|