@frockbot/plugin-provider-frock-ai 0.0.0 → 0.3.13

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/frockbot.json ADDED
@@ -0,0 +1,34 @@
1
+ {
2
+ "schemaVersion": 4,
3
+ "id": "provider-flock-ai",
4
+ "displayName": "Frock AI",
5
+ "version": "0.0.1",
6
+ "compatibility": { "frockbot": ">=0.0.1" },
7
+ "dependencies": { "settings": ">=0.0.1" },
8
+ "contributions": {
9
+ "backend": [{ "entry": "./user", "host": "user" }],
10
+ "runtime": { "entry": "./runtime" }
11
+ },
12
+ "permissions": ["connections:manage", "models:invoke"],
13
+ "configuration": {
14
+ "connectionTypes": [
15
+ {
16
+ "id": "flock-ai-account",
17
+ "displayName": "Frock AI",
18
+ "allowMultiple": false,
19
+ "authorization": { "kind": "ambient-native" },
20
+ "capabilities": ["flock-ai-models"]
21
+ }
22
+ ],
23
+ "capabilities": [
24
+ {
25
+ "id": "flock-ai-models",
26
+ "kind": "model",
27
+ "connectionTypes": ["flock-ai-account"],
28
+ "admission": {
29
+ "turnTypes": ["chat", "automation", "subagent"]
30
+ }
31
+ }
32
+ ]
33
+ }
34
+ }
package/package.json CHANGED
@@ -1,14 +1,42 @@
1
1
  {
2
2
  "name": "@frockbot/plugin-provider-frock-ai",
3
- "version": "0.0.0",
4
- "description": "Placeholder reserving this name for trusted publishing. Superseded by the first release.",
5
- "license": "UNLICENSED",
3
+ "version": "0.3.13",
4
+ "private": false,
5
+ "type": "module",
6
+ "exports": {
7
+ ".": "./src/index.ts",
8
+ "./catalog": "./src/catalog.ts",
9
+ "./runtime": "./src/runtime.ts",
10
+ "./user": "./src/user.ts",
11
+ "./manifest": "./src/manifest.ts",
12
+ "./frockbot.json": "./frockbot.json",
13
+ "./package.json": "./package.json"
14
+ },
15
+ "frockbot": {
16
+ "manifest": "./frockbot.json"
17
+ },
18
+ "scripts": {
19
+ "test": "bun test src",
20
+ "typecheck": "tsc --noEmit -p tsconfig.json"
21
+ },
22
+ "dependencies": {
23
+ "@frockbot/configuration-core": "0.3.13",
24
+ "@frockbot/connection-core": "0.3.13",
25
+ "@frockbot/kernel-contracts": "0.3.13",
26
+ "@frockbot/plugin-models": "0.3.13",
27
+ "@frockbot/provider-openai-compatible": "0.3.13",
28
+ "cordis": "4.0.0-rc.8"
29
+ },
30
+ "devDependencies": {
31
+ "@types/bun": "1.4.0",
32
+ "typescript": "^7.0.2"
33
+ },
34
+ "publishConfig": {
35
+ "access": "public"
36
+ },
6
37
  "repository": {
7
38
  "type": "git",
8
39
  "url": "git+https://github.com/timoconnellaus/frockbot.git",
9
40
  "directory": "packages/plugin-provider-frock-ai"
10
- },
11
- "publishConfig": {
12
- "access": "public"
13
41
  }
14
42
  }
@@ -0,0 +1,82 @@
1
+ import { describe, expect, test } from "bun:test";
2
+ import {
3
+ FROCK_AI_DEFAULT_MODEL,
4
+ cloudflareModelIdForFrockIdV1,
5
+ frockAiStaticCatalogV1,
6
+ frockModelIdForCloudflareIdV1,
7
+ gatewayModelForFrockIdV1,
8
+ normalizeFrockModelIdV1,
9
+ } from "./catalog.js";
10
+
11
+ describe("Frock AI catalog", () => {
12
+ test("presents Cloudflare ids as Frock AI ids and maps them back", () => {
13
+ const cloudflareId = "@cf/deepseek-ai/deepseek-v4-flash-0731";
14
+ const frockId = "@frock/deepseek-ai/deepseek-v4-flash-0731";
15
+
16
+ expect(frockModelIdForCloudflareIdV1(cloudflareId)).toBe(frockId);
17
+ expect(cloudflareModelIdForFrockIdV1(frockId)).toBe(cloudflareId);
18
+ expect(gatewayModelForFrockIdV1(frockId)).toBe(
19
+ `workers-ai/${cloudflareId}`,
20
+ );
21
+ });
22
+
23
+ test("lists Auto first and never exposes a Cloudflare id", () => {
24
+ const catalog = frockAiStaticCatalogV1();
25
+
26
+ expect(catalog.models[0]).toMatchObject({
27
+ providerModelId: FROCK_AI_DEFAULT_MODEL,
28
+ displayName: "Auto (recommended)",
29
+ });
30
+ expect(
31
+ catalog.models.every((model) =>
32
+ model.providerModelId.startsWith("@frock/"),
33
+ ),
34
+ ).toBe(true);
35
+ });
36
+
37
+ test("maps Auto to the configured dynamic route", () => {
38
+ expect(gatewayModelForFrockIdV1(FROCK_AI_DEFAULT_MODEL)).toBe(
39
+ "dynamic/flock-auto",
40
+ );
41
+ expect(
42
+ gatewayModelForFrockIdV1(FROCK_AI_DEFAULT_MODEL, "production-auto"),
43
+ ).toBe("dynamic/production-auto");
44
+ });
45
+
46
+ test("rejects ids outside the Frock AI namespace", () => {
47
+ expect(() => gatewayModelForFrockIdV1("@cf/not/frock")).toThrow(
48
+ 'must start with "@frock/"',
49
+ );
50
+ expect(() => cloudflareModelIdForFrockIdV1("not-frock")).toThrow(
51
+ 'must start with "@frock/"',
52
+ );
53
+ expect(() => frockModelIdForCloudflareIdV1("@frock/not-cf")).toThrow(
54
+ 'must start with "@cf/"',
55
+ );
56
+ });
57
+
58
+ test("reads a pre-rename @flock/ id as the @frock/ id it always meant", () => {
59
+ expect(normalizeFrockModelIdV1("@flock/auto")).toBe(FROCK_AI_DEFAULT_MODEL);
60
+ expect(
61
+ normalizeFrockModelIdV1("@flock/deepseek-ai/deepseek-v4-flash-0731"),
62
+ ).toBe("@frock/deepseek-ai/deepseek-v4-flash-0731");
63
+ expect(normalizeFrockModelIdV1("@frock/auto")).toBe("@frock/auto");
64
+ expect(normalizeFrockModelIdV1("gpt-4o")).toBe("gpt-4o");
65
+ });
66
+
67
+ test("resolves a legacy id to the same gateway model as its new spelling", () => {
68
+ expect(gatewayModelForFrockIdV1("@flock/auto")).toBe(
69
+ gatewayModelForFrockIdV1(FROCK_AI_DEFAULT_MODEL),
70
+ );
71
+ expect(
72
+ gatewayModelForFrockIdV1("@flock/deepseek-ai/deepseek-v4-flash-0731"),
73
+ ).toBe(
74
+ gatewayModelForFrockIdV1("@frock/deepseek-ai/deepseek-v4-flash-0731"),
75
+ );
76
+ expect(
77
+ cloudflareModelIdForFrockIdV1(
78
+ "@flock/deepseek-ai/deepseek-v4-flash-0731",
79
+ ),
80
+ ).toBe("@cf/deepseek-ai/deepseek-v4-flash-0731");
81
+ });
82
+ });
package/src/catalog.ts ADDED
@@ -0,0 +1,112 @@
1
+ import type { ConnectionModelCatalogV1 } from "@frockbot/connection-core";
2
+
3
+ // The provider is Frock AI. Its *stored* identity strings still read `flock-`:
4
+ // every existing User has them written into their Connection, their installed
5
+ // Package list and their Bots' model bindings, and renaming a stored id is a
6
+ // data migration rather than a rename. They are never shown to a person — the
7
+ // display name and the model ids are, and those moved.
8
+ export const FROCK_AI_PACKAGE_ID = "provider-flock-ai";
9
+ export const FROCK_AI_CONNECTION_TYPE_ID = "flock-ai-account";
10
+ export const FROCK_AI_CAPABILITY_ID = "flock-ai-models";
11
+ export const FROCK_AI_PROVIDER_TYPE = "flock-ai";
12
+ export const FROCK_AI_CONNECTION_ID = "flock-ai-ambient";
13
+ export const FROCK_AI_CONNECTION_GENERATION = "flock-ai-ambient-v1";
14
+ export const FROCK_AI_DEFAULT_MODEL = "@frock/auto";
15
+ /**
16
+ * The Cloudflare AI Gateway dynamic route. The dashboard resource is still
17
+ * named `flock-auto`; the value is the resource's name, not ours.
18
+ */
19
+ export const FROCK_AI_DEFAULT_AUTO_ROUTE = "flock-auto";
20
+
21
+ /** The pre-rename model-id prefix. Bots bound before the rename still carry it. */
22
+ export const FROCK_AI_LEGACY_MODEL_PREFIX = "@flock/";
23
+ export const FROCK_AI_MODEL_PREFIX = "@frock/";
24
+
25
+ /**
26
+ * Read a Frock AI model id, accepting the pre-rename `@flock/` spelling.
27
+ * Every decode and resolution path runs ids through this, so a Bot bound to
28
+ * `@flock/auto` behaves exactly as one bound to `@frock/auto`; nothing writes
29
+ * the legacy prefix back.
30
+ */
31
+ export function normalizeFrockModelIdV1(id: string): string {
32
+ return id.startsWith(FROCK_AI_LEGACY_MODEL_PREFIX)
33
+ ? `${FROCK_AI_MODEL_PREFIX}${id.slice(FROCK_AI_LEGACY_MODEL_PREFIX.length)}`
34
+ : id;
35
+ }
36
+
37
+ /** Whether an id names a Frock AI model under either spelling. */
38
+ export function isFrockModelIdV1(id: string): boolean {
39
+ const normalized = normalizeFrockModelIdV1(id);
40
+ return (
41
+ normalized.startsWith(FROCK_AI_MODEL_PREFIX) &&
42
+ normalized.length > FROCK_AI_MODEL_PREFIX.length
43
+ );
44
+ }
45
+
46
+ const CLOUDFLARE_TEXT_MODELS = [
47
+ {
48
+ cloudflareModelId: "@cf/deepseek-ai/deepseek-v4-flash-0731",
49
+ displayName: "DeepSeek V4 Flash",
50
+ capabilities: { tools: true, vision: false, reasoning: true },
51
+ },
52
+ ] as const;
53
+
54
+ export function frockModelIdForCloudflareIdV1(id: string): string {
55
+ if (!id.startsWith("@cf/") || id.length === "@cf/".length) {
56
+ throw new Error(`Cloudflare model id "${id}" must start with "@cf/"`);
57
+ }
58
+ return `@frock/${id.slice("@cf/".length)}`;
59
+ }
60
+
61
+ export function cloudflareModelIdForFrockIdV1(input: string): string {
62
+ const id = normalizeFrockModelIdV1(input);
63
+ if (!isFrockModelIdV1(id)) {
64
+ throw new Error(`Frock AI model id "${input}" must start with "@frock/"`);
65
+ }
66
+ if (id === FROCK_AI_DEFAULT_MODEL) {
67
+ throw new Error("Frock AI Auto does not name a Cloudflare model");
68
+ }
69
+ return `@cf/${id.slice(FROCK_AI_MODEL_PREFIX.length)}`;
70
+ }
71
+
72
+ export function gatewayModelForFrockIdV1(
73
+ input: string,
74
+ autoRoute = FROCK_AI_DEFAULT_AUTO_ROUTE,
75
+ ): string {
76
+ const id = normalizeFrockModelIdV1(input);
77
+ if (!isFrockModelIdV1(id)) {
78
+ throw new Error(`Frock AI model id "${input}" must start with "@frock/"`);
79
+ }
80
+ if (id === FROCK_AI_DEFAULT_MODEL) {
81
+ if (!/^[A-Za-z0-9-]+$/.test(autoRoute)) {
82
+ throw new Error(`Frock AI Auto route "${autoRoute}" is invalid`);
83
+ }
84
+ return `dynamic/${autoRoute}`;
85
+ }
86
+ return `workers-ai/${cloudflareModelIdForFrockIdV1(id)}`;
87
+ }
88
+
89
+ const STATIC_CATALOG: ConnectionModelCatalogV1 = {
90
+ schemaVersion: 1,
91
+ generation: "flock-ai-static-v1",
92
+ state: "fresh",
93
+ models: [
94
+ {
95
+ providerModelId: FROCK_AI_DEFAULT_MODEL,
96
+ displayName: "Auto (recommended)",
97
+ capabilities: { tools: true, vision: false, reasoning: true },
98
+ source: "discovered",
99
+ },
100
+ ...CLOUDFLARE_TEXT_MODELS.map((model) => ({
101
+ providerModelId: frockModelIdForCloudflareIdV1(model.cloudflareModelId),
102
+ displayName: model.displayName,
103
+ capabilities: model.capabilities,
104
+ source: "discovered" as const,
105
+ })),
106
+ ],
107
+ };
108
+
109
+ /** A deployment-safe advisory catalog; it requires no account token or REST call. */
110
+ export function frockAiStaticCatalogV1(): ConnectionModelCatalogV1 {
111
+ return structuredClone(STATIC_CATALOG);
112
+ }
package/src/index.ts ADDED
@@ -0,0 +1,2 @@
1
+ export * from "./catalog.js";
2
+ export * from "./runtime.js";
@@ -0,0 +1,3 @@
1
+ import manifest from "../frockbot.json" with { type: "json" };
2
+
3
+ export default manifest;