From 00247a8d69613a4cfeb621b5cca075828e5ae1e1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Cruz?= Date: Fri, 22 Sep 2023 13:31:37 +0100 Subject: [PATCH] wrangler: add AI related commands (#3986) * wrangler: add AI related commands * wrangler: truncate based on terminal size --- .changeset/orange-lizards-joke.md | 5 + packages/wrangler/src/__tests__/ai.test.ts | 844 ++++++++++++++++++ .../wrangler/src/__tests__/deploy.test.ts | 4 +- packages/wrangler/src/__tests__/index.test.ts | 2 + .../src/__tests__/mtls-certificates.test.ts | 1 + packages/wrangler/src/__tests__/user.test.ts | 2 +- packages/wrangler/src/ai/index.ts | 11 + packages/wrangler/src/ai/listCatalog.tsx | 55 ++ packages/wrangler/src/ai/options.ts | 17 + packages/wrangler/src/ai/types.ts | 14 + packages/wrangler/src/ai/utils.ts | 37 + packages/wrangler/src/index.ts | 6 + packages/wrangler/src/user/user.ts | 1 + 13 files changed, 996 insertions(+), 3 deletions(-) create mode 100644 .changeset/orange-lizards-joke.md create mode 100644 packages/wrangler/src/__tests__/ai.test.ts create mode 100644 packages/wrangler/src/ai/index.ts create mode 100644 packages/wrangler/src/ai/listCatalog.tsx create mode 100644 packages/wrangler/src/ai/options.ts create mode 100644 packages/wrangler/src/ai/types.ts create mode 100644 packages/wrangler/src/ai/utils.ts diff --git a/.changeset/orange-lizards-joke.md b/.changeset/orange-lizards-joke.md new file mode 100644 index 000000000000..6c1513d15a71 --- /dev/null +++ b/.changeset/orange-lizards-joke.md @@ -0,0 +1,5 @@ +--- +"wrangler": patch +--- + +Added AI related CLI commands diff --git a/packages/wrangler/src/__tests__/ai.test.ts b/packages/wrangler/src/__tests__/ai.test.ts new file mode 100644 index 000000000000..d09c9c38cd49 --- /dev/null +++ b/packages/wrangler/src/__tests__/ai.test.ts @@ -0,0 +1,844 @@ +import { rest } from "msw"; +import { endEventLoop } from "./helpers/end-event-loop"; +import { mockAccountId, mockApiToken } from "./helpers/mock-account-id"; +import { mockConsoleMethods } from "./helpers/mock-console"; +import { clearDialogs } from "./helpers/mock-dialogs"; +import { useMockIsTTY } from "./helpers/mock-istty"; +import { createFetchResult, msw } from "./helpers/msw"; +import { runInTempDir } from "./helpers/run-in-tmp"; +import { runWrangler } from "./helpers/run-wrangler"; + +describe("ai help", () => { + const std = mockConsoleMethods(); + runInTempDir(); + + it("should show help when no argument is passed", async () => { + await runWrangler("ai"); + await endEventLoop(); + + expect(std.out).toMatchInlineSnapshot(` + "wrangler ai + + 🤖 Interact with AI models + + Commands: + wrangler ai models List catalog models + + Flags: + -j, --experimental-json-config Experimental: Support wrangler.json [boolean] + -c, --config Path to .toml configuration file [string] + -e, --env Environment to use for operations and .env files [string] + -h, --help Show help [boolean] + -v, --version Show version number [boolean]" + `); + }); + + it("should show help when an invalid argument is passed", async () => { + await expect(() => runWrangler("ai asdf")).rejects.toThrow( + "Unknown argument: asdf" + ); + + expect(std.err).toMatchInlineSnapshot(` + "X [ERROR] Unknown argument: asdf + + " + `); + expect(std.out).toMatchInlineSnapshot(` + " + wrangler ai + + 🤖 Interact with AI models + + Commands: + wrangler ai models List catalog models + + Flags: + -j, --experimental-json-config Experimental: Support wrangler.json [boolean] + -c, --config Path to .toml configuration file [string] + -e, --env Environment to use for operations and .env files [string] + -h, --help Show help [boolean] + -v, --version Show version number [boolean]" + `); + }); +}); + +describe("ai commands", () => { + mockAccountId(); + mockApiToken(); + runInTempDir(); + const { setIsTTY } = useMockIsTTY(); + + const std = mockConsoleMethods(); + + beforeEach(() => { + setIsTTY(true); + }); + + afterEach(() => { + clearDialogs(); + }); + + it("should handle model list", async () => { + mockAIRequest(); + await runWrangler("ai models"); + expect(std.out).toMatchInlineSnapshot(` + "┌──────────────────────────────────────┬─────────────────────────────────────┬─────────────┬──────────────────────┐ + │ model │ name │ description │ task │ + ├──────────────────────────────────────┼─────────────────────────────────────┼─────────────┼──────────────────────┤ + │ 429b9e8b-d99e-44de-91ad-706cf8183658 │ @cloudflare/embeddings_bge_large_en │ │ │ + ├──────────────────────────────────────┼─────────────────────────────────────┼─────────────┼──────────────────────┤ + │ 7f9a76e1-d120-48dd-a565-101d328bbb02 │ @cloudflare/resnet50 │ │ Image Classification │ + └──────────────────────────────────────┴─────────────────────────────────────┴─────────────┴──────────────────────┘" + `); + }); + + it("should truncate model description", async () => { + mockAIOverflowRequest(); + await runWrangler("ai models"); + expect(std.out).toMatchInlineSnapshot(` + "┌──────────────────────────────────────┬─────────────────────────────────────┬─────────────────────────────────────────────────────────────────────────────────────────────────────────┬──────────────────────┐ + │ model │ name │ description │ task │ + ├──────────────────────────────────────┼─────────────────────────────────────┼─────────────────────────────────────────────────────────────────────────────────────────────────────────┼──────────────────────┤ + │ 429b9e8b-d99e-44de-91ad-706cf8183658 │ @cloudflare/embeddings_bge_large_en │ │ │ + ├──────────────────────────────────────┼─────────────────────────────────────┼─────────────────────────────────────────────────────────────────────────────────────────────────────────┼──────────────────────┤ + │ 7f9a76e1-d120-48dd-a565-101d328bbb02 │ @cloudflare/resnet50 │ overflowoverflowoverflowoverflowoverflowoverflowoverflowoverflowoverflowoverflowoverflowoverflowover... │ Image Classification │ + └──────────────────────────────────────┴─────────────────────────────────────┴─────────────────────────────────────────────────────────────────────────────────────────────────────────┴──────────────────────┘" + `); + }); + + it("should paginate results", async () => { + mockAIPaginatedRequest(); + await runWrangler("ai models"); + expect(std.out).toMatchInlineSnapshot(` + "┌──────────────────────────────────────┬─────────────────────────────────────┬─────────────┬──────────────────────┐ + │ model │ name │ description │ task │ + ├──────────────────────────────────────┼─────────────────────────────────────┼─────────────┼──────────────────────┤ + │ 429b9e8b-d99e-44de-91ad-706cf8183658 │ @cloudflare/embeddings_bge_large_en │ │ │ + ├──────────────────────────────────────┼─────────────────────────────────────┼─────────────┼──────────────────────┤ + │ 7f9a76e1-d120-48dd-a565-101d328bbb02 │ @cloudflare/resnet50 │ │ Image Classification │ + ├──────────────────────────────────────┼─────────────────────────────────────┼─────────────┼──────────────────────┤ + │ 429b9e8b-d99e-44de-91ad-706cf8183658 │ @cloudflare/embeddings_bge_large_en │ │ │ + ├──────────────────────────────────────┼─────────────────────────────────────┼─────────────┼──────────────────────┤ + │ 7f9a76e1-d120-48dd-a565-101d328bbb02 │ @cloudflare/resnet50 │ │ Image Classification │ + ├──────────────────────────────────────┼─────────────────────────────────────┼─────────────┼──────────────────────┤ + │ 429b9e8b-d99e-44de-91ad-706cf8183658 │ @cloudflare/embeddings_bge_large_en │ │ │ + ├──────────────────────────────────────┼─────────────────────────────────────┼─────────────┼──────────────────────┤ + │ 7f9a76e1-d120-48dd-a565-101d328bbb02 │ @cloudflare/resnet50 │ │ Image Classification │ + ├──────────────────────────────────────┼─────────────────────────────────────┼─────────────┼──────────────────────┤ + │ 429b9e8b-d99e-44de-91ad-706cf8183658 │ @cloudflare/embeddings_bge_large_en │ │ │ + ├──────────────────────────────────────┼─────────────────────────────────────┼─────────────┼──────────────────────┤ + │ 7f9a76e1-d120-48dd-a565-101d328bbb02 │ @cloudflare/resnet50 │ │ Image Classification │ + ├──────────────────────────────────────┼─────────────────────────────────────┼─────────────┼──────────────────────┤ + │ 429b9e8b-d99e-44de-91ad-706cf8183658 │ @cloudflare/embeddings_bge_large_en │ │ │ + ├──────────────────────────────────────┼─────────────────────────────────────┼─────────────┼──────────────────────┤ + │ 7f9a76e1-d120-48dd-a565-101d328bbb02 │ @cloudflare/resnet50 │ │ Image Classification │ + ├──────────────────────────────────────┼─────────────────────────────────────┼─────────────┼──────────────────────┤ + │ 429b9e8b-d99e-44de-91ad-706cf8183658 │ @cloudflare/embeddings_bge_large_en │ │ │ + ├──────────────────────────────────────┼─────────────────────────────────────┼─────────────┼──────────────────────┤ + │ 7f9a76e1-d120-48dd-a565-101d328bbb02 │ @cloudflare/resnet50 │ │ Image Classification │ + ├──────────────────────────────────────┼─────────────────────────────────────┼─────────────┼──────────────────────┤ + │ 429b9e8b-d99e-44de-91ad-706cf8183658 │ @cloudflare/embeddings_bge_large_en │ │ │ + ├──────────────────────────────────────┼─────────────────────────────────────┼─────────────┼──────────────────────┤ + │ 7f9a76e1-d120-48dd-a565-101d328bbb02 │ @cloudflare/resnet50 │ │ Image Classification │ + ├──────────────────────────────────────┼─────────────────────────────────────┼─────────────┼──────────────────────┤ + │ 429b9e8b-d99e-44de-91ad-706cf8183658 │ @cloudflare/embeddings_bge_large_en │ │ │ + ├──────────────────────────────────────┼─────────────────────────────────────┼─────────────┼──────────────────────┤ + │ 7f9a76e1-d120-48dd-a565-101d328bbb02 │ @cloudflare/resnet50 │ │ Image Classification │ + ├──────────────────────────────────────┼─────────────────────────────────────┼─────────────┼──────────────────────┤ + │ 429b9e8b-d99e-44de-91ad-706cf8183658 │ @cloudflare/embeddings_bge_large_en │ │ │ + ├──────────────────────────────────────┼─────────────────────────────────────┼─────────────┼──────────────────────┤ + │ 7f9a76e1-d120-48dd-a565-101d328bbb02 │ @cloudflare/resnet50 │ │ Image Classification │ + ├──────────────────────────────────────┼─────────────────────────────────────┼─────────────┼──────────────────────┤ + │ 429b9e8b-d99e-44de-91ad-706cf8183658 │ @cloudflare/embeddings_bge_large_en │ │ │ + ├──────────────────────────────────────┼─────────────────────────────────────┼─────────────┼──────────────────────┤ + │ 7f9a76e1-d120-48dd-a565-101d328bbb02 │ @cloudflare/resnet50 │ │ Image Classification │ + ├──────────────────────────────────────┼─────────────────────────────────────┼─────────────┼──────────────────────┤ + │ 429b9e8b-d99e-44de-91ad-706cf8183658 │ @cloudflare/embeddings_bge_large_en │ │ │ + ├──────────────────────────────────────┼─────────────────────────────────────┼─────────────┼──────────────────────┤ + │ 7f9a76e1-d120-48dd-a565-101d328bbb02 │ @cloudflare/resnet50 │ │ Image Classification │ + ├──────────────────────────────────────┼─────────────────────────────────────┼─────────────┼──────────────────────┤ + │ 429b9e8b-d99e-44de-91ad-706cf8183658 │ @cloudflare/embeddings_bge_large_en │ │ │ + ├──────────────────────────────────────┼─────────────────────────────────────┼─────────────┼──────────────────────┤ + │ 7f9a76e1-d120-48dd-a565-101d328bbb02 │ @cloudflare/resnet50 │ │ Image Classification │ + ├──────────────────────────────────────┼─────────────────────────────────────┼─────────────┼──────────────────────┤ + │ 429b9e8b-d99e-44de-91ad-706cf8183658 │ @cloudflare/embeddings_bge_large_en │ │ │ + ├──────────────────────────────────────┼─────────────────────────────────────┼─────────────┼──────────────────────┤ + │ 7f9a76e1-d120-48dd-a565-101d328bbb02 │ @cloudflare/resnet50 │ │ Image Classification │ + ├──────────────────────────────────────┼─────────────────────────────────────┼─────────────┼──────────────────────┤ + │ 429b9e8b-d99e-44de-91ad-706cf8183658 │ @cloudflare/embeddings_bge_large_en │ │ │ + ├──────────────────────────────────────┼─────────────────────────────────────┼─────────────┼──────────────────────┤ + │ 7f9a76e1-d120-48dd-a565-101d328bbb02 │ @cloudflare/resnet50 │ │ Image Classification │ + ├──────────────────────────────────────┼─────────────────────────────────────┼─────────────┼──────────────────────┤ + │ 429b9e8b-d99e-44de-91ad-706cf8183658 │ @cloudflare/embeddings_bge_large_en │ │ │ + ├──────────────────────────────────────┼─────────────────────────────────────┼─────────────┼──────────────────────┤ + │ 7f9a76e1-d120-48dd-a565-101d328bbb02 │ @cloudflare/resnet50 │ │ Image Classification │ + ├──────────────────────────────────────┼─────────────────────────────────────┼─────────────┼──────────────────────┤ + │ 429b9e8b-d99e-44de-91ad-706cf8183658 │ @cloudflare/embeddings_bge_large_en │ │ │ + ├──────────────────────────────────────┼─────────────────────────────────────┼─────────────┼──────────────────────┤ + │ 7f9a76e1-d120-48dd-a565-101d328bbb02 │ @cloudflare/resnet50 │ │ Image Classification │ + ├──────────────────────────────────────┼─────────────────────────────────────┼─────────────┼──────────────────────┤ + │ 429b9e8b-d99e-44de-91ad-706cf8183658 │ @cloudflare/embeddings_bge_large_en │ │ │ + ├──────────────────────────────────────┼─────────────────────────────────────┼─────────────┼──────────────────────┤ + │ 7f9a76e1-d120-48dd-a565-101d328bbb02 │ @cloudflare/resnet50 │ │ Image Classification │ + ├──────────────────────────────────────┼─────────────────────────────────────┼─────────────┼──────────────────────┤ + │ 429b9e8b-d99e-44de-91ad-706cf8183658 │ @cloudflare/embeddings_bge_large_en │ │ │ + ├──────────────────────────────────────┼─────────────────────────────────────┼─────────────┼──────────────────────┤ + │ 7f9a76e1-d120-48dd-a565-101d328bbb02 │ @cloudflare/resnet50 │ │ Image Classification │ + ├──────────────────────────────────────┼─────────────────────────────────────┼─────────────┼──────────────────────┤ + │ 429b9e8b-d99e-44de-91ad-706cf8183658 │ @cloudflare/embeddings_bge_large_en │ │ │ + ├──────────────────────────────────────┼─────────────────────────────────────┼─────────────┼──────────────────────┤ + │ 7f9a76e1-d120-48dd-a565-101d328bbb02 │ @cloudflare/resnet50 │ │ Image Classification │ + ├──────────────────────────────────────┼─────────────────────────────────────┼─────────────┼──────────────────────┤ + │ 429b9e8b-d99e-44de-91ad-706cf8183658 │ @cloudflare/embeddings_bge_large_en │ │ │ + ├──────────────────────────────────────┼─────────────────────────────────────┼─────────────┼──────────────────────┤ + │ 7f9a76e1-d120-48dd-a565-101d328bbb02 │ @cloudflare/resnet50 │ │ Image Classification │ + ├──────────────────────────────────────┼─────────────────────────────────────┼─────────────┼──────────────────────┤ + │ 429b9e8b-d99e-44de-91ad-706cf8183658 │ @cloudflare/embeddings_bge_large_en │ │ │ + ├──────────────────────────────────────┼─────────────────────────────────────┼─────────────┼──────────────────────┤ + │ 7f9a76e1-d120-48dd-a565-101d328bbb02 │ @cloudflare/resnet50 │ │ Image Classification │ + ├──────────────────────────────────────┼─────────────────────────────────────┼─────────────┼──────────────────────┤ + │ 429b9e8b-d99e-44de-91ad-706cf8183658 │ @cloudflare/embeddings_bge_large_en │ │ │ + ├──────────────────────────────────────┼─────────────────────────────────────┼─────────────┼──────────────────────┤ + │ 7f9a76e1-d120-48dd-a565-101d328bbb02 │ @cloudflare/resnet50 │ │ Image Classification │ + ├──────────────────────────────────────┼─────────────────────────────────────┼─────────────┼──────────────────────┤ + │ 429b9e8b-d99e-44de-91ad-706cf8183658 │ @cloudflare/embeddings_bge_large_en │ │ │ + ├──────────────────────────────────────┼─────────────────────────────────────┼─────────────┼──────────────────────┤ + │ 7f9a76e1-d120-48dd-a565-101d328bbb02 │ @cloudflare/resnet50 │ │ Image Classification │ + ├──────────────────────────────────────┼─────────────────────────────────────┼─────────────┼──────────────────────┤ + │ 429b9e8b-d99e-44de-91ad-706cf8183658 │ @cloudflare/embeddings_bge_large_en │ │ │ + ├──────────────────────────────────────┼─────────────────────────────────────┼─────────────┼──────────────────────┤ + │ 7f9a76e1-d120-48dd-a565-101d328bbb02 │ @cloudflare/resnet50 │ │ Image Classification │ + ├──────────────────────────────────────┼─────────────────────────────────────┼─────────────┼──────────────────────┤ + │ 429b9e8b-d99e-44de-91ad-706cf8183658 │ @cloudflare/embeddings_bge_large_en │ │ │ + ├──────────────────────────────────────┼─────────────────────────────────────┼─────────────┼──────────────────────┤ + │ 7f9a76e1-d120-48dd-a565-101d328bbb02 │ @cloudflare/resnet50 │ │ Image Classification │ + ├──────────────────────────────────────┼─────────────────────────────────────┼─────────────┼──────────────────────┤ + │ 429b9e8b-d99e-44de-91ad-706cf8183658 │ @cloudflare/embeddings_bge_large_en │ second page │ │ + ├──────────────────────────────────────┼─────────────────────────────────────┼─────────────┼──────────────────────┤ + │ 7f9a76e1-d120-48dd-a565-101d328bbb02 │ @cloudflare/resnet50 │ second page │ Image Classification │ + └──────────────────────────────────────┴─────────────────────────────────────┴─────────────┴──────────────────────┘" + `); + }); +}); + +/** Create a mock handler for AI API */ +function mockAIRequest() { + msw.use( + rest.get("*/accounts/:accountId/ai/models/search", (req, res, ctx) => { + return res.once( + ctx.json( + createFetchResult( + [ + { + id: "429b9e8b-d99e-44de-91ad-706cf8183658", + source: 1, + task: null, + tags: [], + name: "@cloudflare/embeddings_bge_large_en", + description: null, + }, + { + id: "7f9a76e1-d120-48dd-a565-101d328bbb02", + source: 1, + task: { + id: "00cd182b-bf30-4fc4-8481-84a3ab349657", + name: "Image Classification", + description: null, + }, + tags: [], + name: "@cloudflare/resnet50", + description: null, + }, + ], + true + ) + ) + ); + }) + ); +} + +function mockAIOverflowRequest() { + msw.use( + rest.get("*/accounts/:accountId/ai/models/search", (req, res, ctx) => { + return res.once( + ctx.json( + createFetchResult( + [ + { + id: "429b9e8b-d99e-44de-91ad-706cf8183658", + source: 1, + task: null, + tags: [], + name: "@cloudflare/embeddings_bge_large_en", + description: null, + }, + { + id: "7f9a76e1-d120-48dd-a565-101d328bbb02", + source: 1, + task: { + id: "00cd182b-bf30-4fc4-8481-84a3ab349657", + name: "Image Classification", + description: null, + }, + tags: [], + name: "@cloudflare/resnet50", + description: + "overflowoverflowoverflowoverflowoverflowoverflowoverflowoverflowoverflowoverflowoverflowoverflowoverflowoverflowoverflow", + }, + ], + true + ) + ) + ); + }) + ); +} + +function mockAIPaginatedRequest() { + msw.use( + rest.get("*/accounts/:accountId/ai/models/search", (req, res, ctx) => { + const json = ctx.json( + createFetchResult( + [ + { + id: "429b9e8b-d99e-44de-91ad-706cf8183658", + source: 1, + task: null, + tags: [], + name: "@cloudflare/embeddings_bge_large_en", + description: null, + }, + { + id: "7f9a76e1-d120-48dd-a565-101d328bbb02", + source: 1, + task: { + id: "00cd182b-bf30-4fc4-8481-84a3ab349657", + name: "Image Classification", + description: null, + }, + tags: [], + name: "@cloudflare/resnet50", + description: null, + }, + { + id: "429b9e8b-d99e-44de-91ad-706cf8183658", + source: 1, + task: null, + tags: [], + name: "@cloudflare/embeddings_bge_large_en", + description: null, + }, + { + id: "7f9a76e1-d120-48dd-a565-101d328bbb02", + source: 1, + task: { + id: "00cd182b-bf30-4fc4-8481-84a3ab349657", + name: "Image Classification", + description: null, + }, + tags: [], + name: "@cloudflare/resnet50", + description: null, + }, + { + id: "429b9e8b-d99e-44de-91ad-706cf8183658", + source: 1, + task: null, + tags: [], + name: "@cloudflare/embeddings_bge_large_en", + description: null, + }, + { + id: "7f9a76e1-d120-48dd-a565-101d328bbb02", + source: 1, + task: { + id: "00cd182b-bf30-4fc4-8481-84a3ab349657", + name: "Image Classification", + description: null, + }, + tags: [], + name: "@cloudflare/resnet50", + description: null, + }, + { + id: "429b9e8b-d99e-44de-91ad-706cf8183658", + source: 1, + task: null, + tags: [], + name: "@cloudflare/embeddings_bge_large_en", + description: null, + }, + { + id: "7f9a76e1-d120-48dd-a565-101d328bbb02", + source: 1, + task: { + id: "00cd182b-bf30-4fc4-8481-84a3ab349657", + name: "Image Classification", + description: null, + }, + tags: [], + name: "@cloudflare/resnet50", + description: null, + }, + { + id: "429b9e8b-d99e-44de-91ad-706cf8183658", + source: 1, + task: null, + tags: [], + name: "@cloudflare/embeddings_bge_large_en", + description: null, + }, + { + id: "7f9a76e1-d120-48dd-a565-101d328bbb02", + source: 1, + task: { + id: "00cd182b-bf30-4fc4-8481-84a3ab349657", + name: "Image Classification", + description: null, + }, + tags: [], + name: "@cloudflare/resnet50", + description: null, + }, + { + id: "429b9e8b-d99e-44de-91ad-706cf8183658", + source: 1, + task: null, + tags: [], + name: "@cloudflare/embeddings_bge_large_en", + description: null, + }, + { + id: "7f9a76e1-d120-48dd-a565-101d328bbb02", + source: 1, + task: { + id: "00cd182b-bf30-4fc4-8481-84a3ab349657", + name: "Image Classification", + description: null, + }, + tags: [], + name: "@cloudflare/resnet50", + description: null, + }, + { + id: "429b9e8b-d99e-44de-91ad-706cf8183658", + source: 1, + task: null, + tags: [], + name: "@cloudflare/embeddings_bge_large_en", + description: null, + }, + { + id: "7f9a76e1-d120-48dd-a565-101d328bbb02", + source: 1, + task: { + id: "00cd182b-bf30-4fc4-8481-84a3ab349657", + name: "Image Classification", + description: null, + }, + tags: [], + name: "@cloudflare/resnet50", + description: null, + }, + { + id: "429b9e8b-d99e-44de-91ad-706cf8183658", + source: 1, + task: null, + tags: [], + name: "@cloudflare/embeddings_bge_large_en", + description: null, + }, + { + id: "7f9a76e1-d120-48dd-a565-101d328bbb02", + source: 1, + task: { + id: "00cd182b-bf30-4fc4-8481-84a3ab349657", + name: "Image Classification", + description: null, + }, + tags: [], + name: "@cloudflare/resnet50", + description: null, + }, + { + id: "429b9e8b-d99e-44de-91ad-706cf8183658", + source: 1, + task: null, + tags: [], + name: "@cloudflare/embeddings_bge_large_en", + description: null, + }, + { + id: "7f9a76e1-d120-48dd-a565-101d328bbb02", + source: 1, + task: { + id: "00cd182b-bf30-4fc4-8481-84a3ab349657", + name: "Image Classification", + description: null, + }, + tags: [], + name: "@cloudflare/resnet50", + description: null, + }, + { + id: "429b9e8b-d99e-44de-91ad-706cf8183658", + source: 1, + task: null, + tags: [], + name: "@cloudflare/embeddings_bge_large_en", + description: null, + }, + { + id: "7f9a76e1-d120-48dd-a565-101d328bbb02", + source: 1, + task: { + id: "00cd182b-bf30-4fc4-8481-84a3ab349657", + name: "Image Classification", + description: null, + }, + tags: [], + name: "@cloudflare/resnet50", + description: null, + }, + { + id: "429b9e8b-d99e-44de-91ad-706cf8183658", + source: 1, + task: null, + tags: [], + name: "@cloudflare/embeddings_bge_large_en", + description: null, + }, + { + id: "7f9a76e1-d120-48dd-a565-101d328bbb02", + source: 1, + task: { + id: "00cd182b-bf30-4fc4-8481-84a3ab349657", + name: "Image Classification", + description: null, + }, + tags: [], + name: "@cloudflare/resnet50", + description: null, + }, + { + id: "429b9e8b-d99e-44de-91ad-706cf8183658", + source: 1, + task: null, + tags: [], + name: "@cloudflare/embeddings_bge_large_en", + description: null, + }, + { + id: "7f9a76e1-d120-48dd-a565-101d328bbb02", + source: 1, + task: { + id: "00cd182b-bf30-4fc4-8481-84a3ab349657", + name: "Image Classification", + description: null, + }, + tags: [], + name: "@cloudflare/resnet50", + description: null, + }, + { + id: "429b9e8b-d99e-44de-91ad-706cf8183658", + source: 1, + task: null, + tags: [], + name: "@cloudflare/embeddings_bge_large_en", + description: null, + }, + { + id: "7f9a76e1-d120-48dd-a565-101d328bbb02", + source: 1, + task: { + id: "00cd182b-bf30-4fc4-8481-84a3ab349657", + name: "Image Classification", + description: null, + }, + tags: [], + name: "@cloudflare/resnet50", + description: null, + }, + { + id: "429b9e8b-d99e-44de-91ad-706cf8183658", + source: 1, + task: null, + tags: [], + name: "@cloudflare/embeddings_bge_large_en", + description: null, + }, + { + id: "7f9a76e1-d120-48dd-a565-101d328bbb02", + source: 1, + task: { + id: "00cd182b-bf30-4fc4-8481-84a3ab349657", + name: "Image Classification", + description: null, + }, + tags: [], + name: "@cloudflare/resnet50", + description: null, + }, + { + id: "429b9e8b-d99e-44de-91ad-706cf8183658", + source: 1, + task: null, + tags: [], + name: "@cloudflare/embeddings_bge_large_en", + description: null, + }, + { + id: "7f9a76e1-d120-48dd-a565-101d328bbb02", + source: 1, + task: { + id: "00cd182b-bf30-4fc4-8481-84a3ab349657", + name: "Image Classification", + description: null, + }, + tags: [], + name: "@cloudflare/resnet50", + description: null, + }, + { + id: "429b9e8b-d99e-44de-91ad-706cf8183658", + source: 1, + task: null, + tags: [], + name: "@cloudflare/embeddings_bge_large_en", + description: null, + }, + { + id: "7f9a76e1-d120-48dd-a565-101d328bbb02", + source: 1, + task: { + id: "00cd182b-bf30-4fc4-8481-84a3ab349657", + name: "Image Classification", + description: null, + }, + tags: [], + name: "@cloudflare/resnet50", + description: null, + }, + { + id: "429b9e8b-d99e-44de-91ad-706cf8183658", + source: 1, + task: null, + tags: [], + name: "@cloudflare/embeddings_bge_large_en", + description: null, + }, + { + id: "7f9a76e1-d120-48dd-a565-101d328bbb02", + source: 1, + task: { + id: "00cd182b-bf30-4fc4-8481-84a3ab349657", + name: "Image Classification", + description: null, + }, + tags: [], + name: "@cloudflare/resnet50", + description: null, + }, + { + id: "429b9e8b-d99e-44de-91ad-706cf8183658", + source: 1, + task: null, + tags: [], + name: "@cloudflare/embeddings_bge_large_en", + description: null, + }, + { + id: "7f9a76e1-d120-48dd-a565-101d328bbb02", + source: 1, + task: { + id: "00cd182b-bf30-4fc4-8481-84a3ab349657", + name: "Image Classification", + description: null, + }, + tags: [], + name: "@cloudflare/resnet50", + description: null, + }, + { + id: "429b9e8b-d99e-44de-91ad-706cf8183658", + source: 1, + task: null, + tags: [], + name: "@cloudflare/embeddings_bge_large_en", + description: null, + }, + { + id: "7f9a76e1-d120-48dd-a565-101d328bbb02", + source: 1, + task: { + id: "00cd182b-bf30-4fc4-8481-84a3ab349657", + name: "Image Classification", + description: null, + }, + tags: [], + name: "@cloudflare/resnet50", + description: null, + }, + { + id: "429b9e8b-d99e-44de-91ad-706cf8183658", + source: 1, + task: null, + tags: [], + name: "@cloudflare/embeddings_bge_large_en", + description: null, + }, + { + id: "7f9a76e1-d120-48dd-a565-101d328bbb02", + source: 1, + task: { + id: "00cd182b-bf30-4fc4-8481-84a3ab349657", + name: "Image Classification", + description: null, + }, + tags: [], + name: "@cloudflare/resnet50", + description: null, + }, + { + id: "429b9e8b-d99e-44de-91ad-706cf8183658", + source: 1, + task: null, + tags: [], + name: "@cloudflare/embeddings_bge_large_en", + description: null, + }, + { + id: "7f9a76e1-d120-48dd-a565-101d328bbb02", + source: 1, + task: { + id: "00cd182b-bf30-4fc4-8481-84a3ab349657", + name: "Image Classification", + description: null, + }, + tags: [], + name: "@cloudflare/resnet50", + description: null, + }, + { + id: "429b9e8b-d99e-44de-91ad-706cf8183658", + source: 1, + task: null, + tags: [], + name: "@cloudflare/embeddings_bge_large_en", + description: null, + }, + { + id: "7f9a76e1-d120-48dd-a565-101d328bbb02", + source: 1, + task: { + id: "00cd182b-bf30-4fc4-8481-84a3ab349657", + name: "Image Classification", + description: null, + }, + tags: [], + name: "@cloudflare/resnet50", + description: null, + }, + { + id: "429b9e8b-d99e-44de-91ad-706cf8183658", + source: 1, + task: null, + tags: [], + name: "@cloudflare/embeddings_bge_large_en", + description: null, + }, + { + id: "7f9a76e1-d120-48dd-a565-101d328bbb02", + source: 1, + task: { + id: "00cd182b-bf30-4fc4-8481-84a3ab349657", + name: "Image Classification", + description: null, + }, + tags: [], + name: "@cloudflare/resnet50", + description: null, + }, + { + id: "429b9e8b-d99e-44de-91ad-706cf8183658", + source: 1, + task: null, + tags: [], + name: "@cloudflare/embeddings_bge_large_en", + description: null, + }, + { + id: "7f9a76e1-d120-48dd-a565-101d328bbb02", + source: 1, + task: { + id: "00cd182b-bf30-4fc4-8481-84a3ab349657", + name: "Image Classification", + description: null, + }, + tags: [], + name: "@cloudflare/resnet50", + description: null, + }, + { + id: "429b9e8b-d99e-44de-91ad-706cf8183658", + source: 1, + task: null, + tags: [], + name: "@cloudflare/embeddings_bge_large_en", + description: null, + }, + { + id: "7f9a76e1-d120-48dd-a565-101d328bbb02", + source: 1, + task: { + id: "00cd182b-bf30-4fc4-8481-84a3ab349657", + name: "Image Classification", + description: null, + }, + tags: [], + name: "@cloudflare/resnet50", + description: null, + }, + ], + true + ) + ); + return res.once(json); + }), + rest.get( + "*/accounts/:accountId/ai/models/search?per_page=50&page=2", + (req, res, ctx) => { + const json = ctx.json( + createFetchResult( + [ + { + id: "429b9e8b-d99e-44de-91ad-706cf8183658", + source: 1, + task: null, + tags: [], + name: "@cloudflare/embeddings_bge_large_en", + description: "second page", + }, + { + id: "7f9a76e1-d120-48dd-a565-101d328bbb02", + source: 1, + task: { + id: "00cd182b-bf30-4fc4-8481-84a3ab349657", + name: "Image Classification", + description: null, + }, + tags: [], + name: "@cloudflare/resnet50", + description: "second page", + }, + ], + true + ) + ); + return res.once(json); + } + ) + ); +} diff --git a/packages/wrangler/src/__tests__/deploy.test.ts b/packages/wrangler/src/__tests__/deploy.test.ts index 5bcaa1f313c1..ec7dcfa85bc3 100644 --- a/packages/wrangler/src/__tests__/deploy.test.ts +++ b/packages/wrangler/src/__tests__/deploy.test.ts @@ -143,7 +143,7 @@ describe("deploy", () => { expect(std.out).toMatchInlineSnapshot(` "Attempting to login via OAuth... - Opening a link in your default browser: https://dash.cloudflare.com/oauth2/auth?response_type=code&client_id=54d11594-84e4-41aa-b438-e81b8fa78ee7&redirect_uri=http%3A%2F%2Flocalhost%3A8976%2Foauth%2Fcallback&scope=account%3Aread%20user%3Aread%20workers%3Awrite%20workers_kv%3Awrite%20workers_routes%3Awrite%20workers_scripts%3Awrite%20workers_tail%3Aread%20d1%3Awrite%20pages%3Awrite%20zone%3Aread%20ssl_certs%3Awrite%20constellation%3Awrite%20offline_access&state=MOCK_STATE_PARAM&code_challenge=MOCK_CODE_CHALLENGE&code_challenge_method=S256 + Opening a link in your default browser: https://dash.cloudflare.com/oauth2/auth?response_type=code&client_id=54d11594-84e4-41aa-b438-e81b8fa78ee7&redirect_uri=http%3A%2F%2Flocalhost%3A8976%2Foauth%2Fcallback&scope=account%3Aread%20user%3Aread%20workers%3Awrite%20workers_kv%3Awrite%20workers_routes%3Awrite%20workers_scripts%3Awrite%20workers_tail%3Aread%20d1%3Awrite%20pages%3Awrite%20zone%3Aread%20ssl_certs%3Awrite%20constellation%3Awrite%20ai%3Aread%20offline_access&state=MOCK_STATE_PARAM&code_challenge=MOCK_CODE_CHALLENGE&code_challenge_method=S256 Successfully logged in. Total Upload: xx KiB / gzip: xx KiB Uploaded test-name (TIMINGS) @@ -183,7 +183,7 @@ describe("deploy", () => { expect(std.out).toMatchInlineSnapshot(` "Attempting to login via OAuth... - Opening a link in your default browser: https://dash.staging.cloudflare.com/oauth2/auth?response_type=code&client_id=54d11594-84e4-41aa-b438-e81b8fa78ee7&redirect_uri=http%3A%2F%2Flocalhost%3A8976%2Foauth%2Fcallback&scope=account%3Aread%20user%3Aread%20workers%3Awrite%20workers_kv%3Awrite%20workers_routes%3Awrite%20workers_scripts%3Awrite%20workers_tail%3Aread%20d1%3Awrite%20pages%3Awrite%20zone%3Aread%20ssl_certs%3Awrite%20constellation%3Awrite%20offline_access&state=MOCK_STATE_PARAM&code_challenge=MOCK_CODE_CHALLENGE&code_challenge_method=S256 + Opening a link in your default browser: https://dash.staging.cloudflare.com/oauth2/auth?response_type=code&client_id=54d11594-84e4-41aa-b438-e81b8fa78ee7&redirect_uri=http%3A%2F%2Flocalhost%3A8976%2Foauth%2Fcallback&scope=account%3Aread%20user%3Aread%20workers%3Awrite%20workers_kv%3Awrite%20workers_routes%3Awrite%20workers_scripts%3Awrite%20workers_tail%3Aread%20d1%3Awrite%20pages%3Awrite%20zone%3Aread%20ssl_certs%3Awrite%20constellation%3Awrite%20ai%3Aread%20offline_access&state=MOCK_STATE_PARAM&code_challenge=MOCK_CODE_CHALLENGE&code_challenge_method=S256 Successfully logged in. Total Upload: xx KiB / gzip: xx KiB Uploaded test-name (TIMINGS) diff --git a/packages/wrangler/src/__tests__/index.test.ts b/packages/wrangler/src/__tests__/index.test.ts index 1e1d9e649869..b1b909e127da 100644 --- a/packages/wrangler/src/__tests__/index.test.ts +++ b/packages/wrangler/src/__tests__/index.test.ts @@ -49,6 +49,7 @@ describe("wrangler", () => { wrangler r2 📦 Interact with an R2 store wrangler dispatch-namespace 📦 Interact with a dispatch namespace wrangler d1 🗄 Interact with a D1 database + wrangler ai 🤖 Interact with AI models wrangler constellation 🤖 Interact with Constellation models wrangler pubsub 📮 Interact and manage Pub/Sub Brokers wrangler mtls-certificate 🪪 Manage certificates used for mTLS connections @@ -101,6 +102,7 @@ describe("wrangler", () => { wrangler r2 📦 Interact with an R2 store wrangler dispatch-namespace 📦 Interact with a dispatch namespace wrangler d1 🗄 Interact with a D1 database + wrangler ai 🤖 Interact with AI models wrangler constellation 🤖 Interact with Constellation models wrangler pubsub 📮 Interact and manage Pub/Sub Brokers wrangler mtls-certificate 🪪 Manage certificates used for mTLS connections diff --git a/packages/wrangler/src/__tests__/mtls-certificates.test.ts b/packages/wrangler/src/__tests__/mtls-certificates.test.ts index f28456c380aa..1b364863fbba 100644 --- a/packages/wrangler/src/__tests__/mtls-certificates.test.ts +++ b/packages/wrangler/src/__tests__/mtls-certificates.test.ts @@ -379,6 +379,7 @@ describe("wrangler", () => { wrangler r2 📦 Interact with an R2 store wrangler dispatch-namespace 📦 Interact with a dispatch namespace wrangler d1 🗄 Interact with a D1 database + wrangler ai 🤖 Interact with AI models wrangler constellation 🤖 Interact with Constellation models wrangler pubsub 📮 Interact and manage Pub/Sub Brokers wrangler mtls-certificate 🪪 Manage certificates used for mTLS connections diff --git a/packages/wrangler/src/__tests__/user.test.ts b/packages/wrangler/src/__tests__/user.test.ts index 15efbc682689..4b8323a3cdfd 100644 --- a/packages/wrangler/src/__tests__/user.test.ts +++ b/packages/wrangler/src/__tests__/user.test.ts @@ -60,7 +60,7 @@ describe("User", () => { expect(counter).toBe(1); expect(std.out).toMatchInlineSnapshot(` "Attempting to login via OAuth... - Opening a link in your default browser: https://dash.cloudflare.com/oauth2/auth?response_type=code&client_id=54d11594-84e4-41aa-b438-e81b8fa78ee7&redirect_uri=http%3A%2F%2Flocalhost%3A8976%2Foauth%2Fcallback&scope=account%3Aread%20user%3Aread%20workers%3Awrite%20workers_kv%3Awrite%20workers_routes%3Awrite%20workers_scripts%3Awrite%20workers_tail%3Aread%20d1%3Awrite%20pages%3Awrite%20zone%3Aread%20ssl_certs%3Awrite%20constellation%3Awrite%20offline_access&state=MOCK_STATE_PARAM&code_challenge=MOCK_CODE_CHALLENGE&code_challenge_method=S256 + Opening a link in your default browser: https://dash.cloudflare.com/oauth2/auth?response_type=code&client_id=54d11594-84e4-41aa-b438-e81b8fa78ee7&redirect_uri=http%3A%2F%2Flocalhost%3A8976%2Foauth%2Fcallback&scope=account%3Aread%20user%3Aread%20workers%3Awrite%20workers_kv%3Awrite%20workers_routes%3Awrite%20workers_scripts%3Awrite%20workers_tail%3Aread%20d1%3Awrite%20pages%3Awrite%20zone%3Aread%20ssl_certs%3Awrite%20constellation%3Awrite%20ai%3Aread%20offline_access&state=MOCK_STATE_PARAM&code_challenge=MOCK_CODE_CHALLENGE&code_challenge_method=S256 Successfully logged in." `); expect(readAuthConfigFile()).toEqual({ diff --git a/packages/wrangler/src/ai/index.ts b/packages/wrangler/src/ai/index.ts new file mode 100644 index 000000000000..c9437828f844 --- /dev/null +++ b/packages/wrangler/src/ai/index.ts @@ -0,0 +1,11 @@ +import * as ListCatalog from "./listCatalog"; +import type { CommonYargsArgv } from "../yargs-types"; + +export function ai(yargs: CommonYargsArgv) { + return yargs.command( + "models", + "List catalog models", + ListCatalog.options, + ListCatalog.handler + ); +} diff --git a/packages/wrangler/src/ai/listCatalog.tsx b/packages/wrangler/src/ai/listCatalog.tsx new file mode 100644 index 000000000000..6f371af0a34c --- /dev/null +++ b/packages/wrangler/src/ai/listCatalog.tsx @@ -0,0 +1,55 @@ +import { withConfig } from "../config"; +import { logger } from "../logger"; +import { requireAuth } from "../user"; +import { asJson } from "./options"; +import { listCatalogEntries, truncate } from "./utils"; +import type { + CommonYargsArgv, + StrictYargsOptionsToInterface, +} from "../yargs-types"; + +export function options(yargs: CommonYargsArgv) { + return asJson(yargs); +} + +function truncateDescription( + description: string | undefined, + alreadyUsed: number +): string { + if (description === undefined || description === null) { + return ""; + } + + if (process.stdout.columns === undefined) { + return truncate(description, 100); + } + + return truncate(description, process.stdout.columns - alreadyUsed); +} + +type HandlerOptions = StrictYargsOptionsToInterface; +export const handler = withConfig( + async ({ json, config }): Promise => { + const accountId = await requireAuth(config); + const entries = await listCatalogEntries(accountId); + + if (json) { + logger.log(JSON.stringify(entries, null, 2)); + } else { + logger.table( + entries.map((entry) => ({ + model: entry.id, + name: entry.name, + description: truncateDescription( + entry.description, + entry.id.length + + entry.name.length + + (entry.task ? entry.task.name.length : 0) + + 10 + ), + task: entry.task ? entry.task.name : "", + })) + ); + } + } +); diff --git a/packages/wrangler/src/ai/options.ts b/packages/wrangler/src/ai/options.ts new file mode 100644 index 000000000000..f97625fb5e45 --- /dev/null +++ b/packages/wrangler/src/ai/options.ts @@ -0,0 +1,17 @@ +import type { CommonYargsArgv } from "../yargs-types"; + +export function takeName(yargs: CommonYargsArgv) { + return yargs.positional("name", { + describe: "The name of the project", + type: "string", + demandOption: true, + }); +} + +export function asJson(yargs: CommonYargsArgv) { + return yargs.option("json", { + describe: "return output as clean JSON", + type: "boolean", + default: false, + }); +} diff --git a/packages/wrangler/src/ai/types.ts b/packages/wrangler/src/ai/types.ts new file mode 100644 index 000000000000..82682ac41a04 --- /dev/null +++ b/packages/wrangler/src/ai/types.ts @@ -0,0 +1,14 @@ +export type Task = { + id: string; + name: string; + description: string; +}; + +export type Model = { + id: string; + source: number; + task?: Task; + tags: string[]; + name: string; + description?: string; +}; diff --git a/packages/wrangler/src/ai/utils.ts b/packages/wrangler/src/ai/utils.ts new file mode 100644 index 000000000000..f1df6a382ea2 --- /dev/null +++ b/packages/wrangler/src/ai/utils.ts @@ -0,0 +1,37 @@ +import { fetchResult } from "../cfetch"; +import type { Model } from "./types"; + +export async function aiList( + accountId: string, + partialUrl: string +): Promise> { + const pageSize = 50; + let page = 1; + const results = []; + while (results.length % pageSize === 0) { + const json: Array = await fetchResult( + `/accounts/${accountId}/ai/${partialUrl}`, + {}, + new URLSearchParams({ + per_page: pageSize.toString(), + page: page.toString(), + }) + ); + page++; + results.push(...json); + if (json.length < pageSize) { + break; + } + } + return results; +} + +export const listCatalogEntries = async ( + accountId: string +): Promise> => { + return await aiList(accountId, "models/search"); +}; + +export function truncate(str: string, maxLen: number) { + return str.slice(0, maxLen) + (str.length > maxLen ? "..." : ""); +} diff --git a/packages/wrangler/src/index.ts b/packages/wrangler/src/index.ts index a7a0db21309b..5a6951317a56 100644 --- a/packages/wrangler/src/index.ts +++ b/packages/wrangler/src/index.ts @@ -5,6 +5,7 @@ import supportsColor from "supports-color"; import { ProxyAgent, setGlobalDispatcher } from "undici"; import makeCLI from "yargs"; import { version as wranglerVersion } from "../package.json"; +import { ai } from "./ai"; import { loadDotEnv, readConfig } from "./config"; import { constellation } from "./constellation"; import { d1 } from "./d1"; @@ -451,6 +452,11 @@ export function createCLIParser(argv: string[]) { }); // ai + wrangler.command("ai", "🤖 Interact with AI models", (aiYargs) => { + return ai(aiYargs.command(subHelp)); + }); + + // constellation wrangler.command( "constellation", "🤖 Interact with Constellation models", diff --git a/packages/wrangler/src/user/user.ts b/packages/wrangler/src/user/user.ts index 9ef7043c9c8f..78f3468d8cdf 100644 --- a/packages/wrangler/src/user/user.ts +++ b/packages/wrangler/src/user/user.ts @@ -348,6 +348,7 @@ const Scopes = { "zone:read": "Grants read level access to account zone.", "ssl_certs:write": "See and manage mTLS certificates for your account", "constellation:write": "Manage Constellation projects/models", + "ai:read": "List AI models", } as const; /**