Call flow's API at /v1 from every client

The frontend drops its v2 base and getURL's version switch; lfx,
the SDK, template validation and the OpenAPI token URL name /v1.

Claude-Session: https://claude.ai/code/session_011i2AG9nwpiKUCVxAdDzYuU
This commit is contained in:
2026-09-29 18:34:20 -07:00
parent 5421a384b5
commit 8c119882bc
69 changed files with 158 additions and 168 deletions
+2 -2
View File
@@ -42,7 +42,7 @@ services:
labels:
- traefik.enable=true
- traefik.constraint-label-stack=${TRAEFIK_TAG?Variable not set}
- traefik.http.routers.${STACK_NAME?Variable not set}-backend-http.rule=PathPrefix(`/v1`) || PathPrefix(`/v2`) || PathPrefix(`/docs`) || PathPrefix(`/health`)
- traefik.http.routers.${STACK_NAME?Variable not set}-backend-http.rule=PathPrefix(`/v1`) || PathPrefix(`/docs`) || PathPrefix(`/health`)
- traefik.http.services.${STACK_NAME?Variable not set}-backend.loadbalancer.server.port=7860
frontend:
@@ -56,7 +56,7 @@ services:
labels:
- traefik.enable=true
- traefik.constraint-label-stack=${TRAEFIK_TAG?Variable not set}
- traefik.http.routers.${STACK_NAME?Variable not set}-celeryworker-http.rule=PathPrefix(`/v1`) || PathPrefix(`/v2`) || PathPrefix(`/docs`) || PathPrefix(`/health`)
- traefik.http.routers.${STACK_NAME?Variable not set}-celeryworker-http.rule=PathPrefix(`/v1`) || PathPrefix(`/docs`) || PathPrefix(`/health`)
- traefik.http.services.${STACK_NAME?Variable not set}-celeryworker.loadbalancer.server.port=7860
networks:
+1 -1
View File
@@ -80,7 +80,7 @@ services:
labels:
- traefik.enable=true
- traefik.constraint-label-stack=${TRAEFIK_TAG?Variable not set}
- traefik.http.routers.${STACK_NAME?Variable not set}-backend-http.rule=PathPrefix(`/v1`) || PathPrefix(`/v2`) || PathPrefix(`/docs`) || PathPrefix(`/health`)
- traefik.http.routers.${STACK_NAME?Variable not set}-backend-http.rule=PathPrefix(`/v1`) || PathPrefix(`/docs`) || PathPrefix(`/health`)
- traefik.http.services.${STACK_NAME?Variable not set}-backend.loadbalancer.server.port=7860
db:
-1
View File
@@ -92,7 +92,6 @@ export class Web extends Construct {
defaultBehavior: { origin: s3SpaOrigin },
additionalBehaviors: {
'/v1/*': albBehaviorOptions,
'/v2/*': albBehaviorOptions,
'/health' : albBehaviorOptions,
},
enableLogging: true, // ログ出力設定
+1 -1
View File
@@ -1024,7 +1024,7 @@ async def create_upload_file(
Authorization is handled by the ``get_flow`` dependency, which requires an
authenticated user and verifies flow ownership. Mirrors the
``max_file_size_upload`` guard on the non-deprecated twin at
``/api/v1/files/upload/{flow_id}`` so authenticated callers can't fill
``/v1/files/upload/{flow_id}`` so authenticated callers can't fill
disk through this route either.
"""
try:
+2 -2
View File
@@ -138,7 +138,7 @@ async def handle_list_resources(project_id=None):
####################################################
# When a user uploads a file inside a flow
# (e.g., via the File Read component),
# it hits /v2/files (POST),
# it hits /v1/files (POST),
# which saves files at the user-level.
# So the above query for flow files is not enough.
# So we list all user files for the current user.
@@ -226,7 +226,7 @@ async def handle_read_resource(uri: str, project_id: UUID | str | None = None) -
if flow is None:
# The namespace segment may refer to the user's own bucket (user-level
# files uploaded via /api/v2/files) rather than a flow id.
# files uploaded via /v1/files) rather than a flow id.
if str(current_user.id) != str(namespace_id):
msg = "Resource not found or access denied"
raise ValueError(msg)
+1 -1
View File
@@ -54,7 +54,7 @@ class OAuth2PasswordBearerCookie(OAuth2PasswordBearer):
return None
oauth2_login = OAuth2PasswordBearerCookie(tokenUrl="api/v1/login", auto_error=False)
oauth2_login = OAuth2PasswordBearerCookie(tokenUrl="v1/login", auto_error=False)
API_KEY_NAME = "x-api-key"
@@ -155,7 +155,7 @@ async def validate_flow_execution(
try:
# Create a flow from the template with timeout
create_response = await client.post("api/v1/flows/", json=template_data, headers=headers, timeout=10)
create_response = await client.post("v1/flows/", json=template_data, headers=headers, timeout=10)
if create_response.status_code != 201: # noqa: PLR2004
errors.append(f"{filename}: Failed to create flow: {create_response.status_code}")
@@ -165,7 +165,7 @@ async def validate_flow_execution(
try:
# Build the flow with timeout
build_response = await client.post(f"api/v1/build/{flow_id}/flow", json={}, headers=headers, timeout=10)
build_response = await client.post(f"v1/build/{flow_id}/flow", json={}, headers=headers, timeout=10)
if build_response.status_code != 200: # noqa: PLR2004
errors.append(f"{filename}: Failed to build flow: {build_response.status_code}")
@@ -175,7 +175,7 @@ async def validate_flow_execution(
# Get build events to validate execution
events_headers = {**headers, "Accept": "application/x-ndjson"}
events_response = await client.get(f"api/v1/build/{job_id}/events", headers=events_headers, timeout=10)
events_response = await client.get(f"v1/build/{job_id}/events", headers=events_headers, timeout=10)
if events_response.status_code != 200: # noqa: PLR2004
errors.append(f"{filename}: Failed to get build events: {events_response.status_code}")
@@ -187,7 +187,7 @@ async def validate_flow_execution(
finally:
# Clean up the flow with timeout
try: # noqa: SIM105
await client.delete(f"api/v1/flows/{flow_id}", headers=headers, timeout=10)
await client.delete(f"v1/flows/{flow_id}", headers=headers, timeout=10)
except asyncio.TimeoutError:
# Log but don't fail if cleanup times out
pass
@@ -381,7 +381,7 @@ Use strict equality `autoLogin === false` instead of `!autoLogin`. Treat `null`
### 6.2 API Contracts
#### GET /api/v1/monitor/messages/shared/sessions
#### GET /v1/monitor/messages/shared/sessions
**Purpose**: List session IDs for a shared flow, scoped to the authenticated user.
@@ -397,7 +397,7 @@ source_flow_id: UUID (required) — The original public flow ID from the URL
---
#### GET /api/v1/monitor/messages/shared
#### GET /v1/monitor/messages/shared
**Purpose**: Get messages for a shared flow, scoped to the authenticated user.
@@ -430,7 +430,7 @@ order_by: string (optional, default: "timestamp") — Allowed: timestamp, sender
---
#### PUT /api/v1/monitor/messages/shared/{message_id}
#### PUT /v1/monitor/messages/shared/{message_id}
**Purpose**: Update a message on a shared flow (e.g., persist `build_duration`).
@@ -443,7 +443,7 @@ order_by: string (optional, default: "timestamp") — Allowed: timestamp, sender
---
#### DELETE /api/v1/monitor/messages/shared/session/{session_id}
#### DELETE /v1/monitor/messages/shared/session/{session_id}
**Purpose**: Delete all messages in a session on a shared flow.
@@ -453,7 +453,7 @@ order_by: string (optional, default: "timestamp") — Allowed: timestamp, sender
---
#### PATCH /api/v1/monitor/messages/shared/session/{old_session_id}
#### PATCH /v1/monitor/messages/shared/session/{old_session_id}
**Purpose**: Rename a session on a shared flow.
@@ -26,7 +26,7 @@ jest
});
jest.mock("@/customization/config-constants", () => ({
BASE_URL_API: "http://localhost:7860/api/v1/",
BASE_URL_API: "http://localhost:7860/v1/",
}));
// Mocking the customization layer for the user avatar is how we prove the
@@ -154,7 +154,7 @@ export default function ModelInputComponent({
// This ensures importing a flow shows the importing user's full enabled
// list rather than the intersection of the two sets.
//
// Cross-type guard: `providersData` from ``GET /api/v1/models`` is NOT
// Cross-type guard: `providersData` from ``GET /v1/models`` is NOT
// filtered by ``model_type`` (the hook doesn't pass one), and the merged
// ``enabled_models`` map treats llm + embeddings as a single flat
// provider→name→bool record. Without this check, text-embedding models
@@ -266,7 +266,7 @@ describe("file-utils", () => {
it("should_not_treat_flow_substrings_as_special_path_segments", () => {
const windowsPath = "C:\\temp\\flow-report\\flow123\\image.png";
const expected =
"http://localhost:3000/api/v1/files/images/C%3A/temp/flow-report/flow123/image.png";
"http://localhost:3000/v1/files/images/C%3A/temp/flow-report/flow123/image.png";
expect(getFilePreviewUrl(windowsPath)).toBe(expected);
});
+1 -6
View File
@@ -1,9 +1,6 @@
// src/constants/constants.ts
import {
BASE_URL_API as CUSTOM_BASE_URL_API,
BASE_URL_API_V2 as CUSTOM_BASE_URL_API_V2,
} from "../customization/config-constants";
import { BASE_URL_API as CUSTOM_BASE_URL_API } from "../customization/config-constants";
import { customDefaultShortcuts } from "../customization/constants";
import type { languageMap } from "../types/components";
@@ -607,8 +604,6 @@ export const ADMIN_HEADER_DESCRIPTION =
export const BASE_URL_API = CUSTOM_BASE_URL_API || "/v1/";
export const BASE_URL_API_V2 = CUSTOM_BASE_URL_API_V2 || "/v2/";
/**
* URLs excluded from error retries.
* @constant
@@ -10,7 +10,7 @@ jest.mock("@/controllers/API/api", () => ({
}));
jest.mock("@/controllers/API/helpers/constants", () => ({
getURL: (key: string) => `api/v1/${key.toLowerCase()}`,
getURL: (key: string) => `v1/${key.toLowerCase()}`,
}));
jest.mock("@/modals/IOModal/helpers/playground-auth", () => ({
@@ -60,7 +60,7 @@ describe("persistMessageProperties", () => {
expect(mockPut).toHaveBeenCalledTimes(1);
expect(mockPut).toHaveBeenCalledWith(
`api/v1/messages/${MESSAGE_ID}`,
`v1/messages/${MESSAGE_ID}`,
PAYLOAD,
);
});
@@ -73,7 +73,7 @@ describe("persistMessageProperties", () => {
expect(mockPut).toHaveBeenCalledTimes(1);
expect(mockPut).toHaveBeenCalledWith(
`api/v1/messages/shared/${MESSAGE_ID}`,
`v1/messages/shared/${MESSAGE_ID}`,
PAYLOAD,
{ params: { source_flow_id: "real-flow-id-123" } },
);
@@ -122,7 +122,7 @@ describe("persistMessageProperties", () => {
persistMessageProperties(MESSAGE_ID, complexPayload);
expect(mockPut).toHaveBeenCalledWith(
`api/v1/messages/${MESSAGE_ID}`,
`v1/messages/${MESSAGE_ID}`,
complexPayload,
);
});
@@ -1,5 +1,4 @@
import { getBaseUrl } from "@/customization/utils/urls";
import { BASE_URL_API_V2 } from "../../../constants/constants";
export const URLs = {
TRANSACTIONS: `monitor/transactions`,
@@ -50,13 +49,12 @@ export const URLs = {
export function getURL(
key: keyof typeof URLs,
params: Record<string, unknown> = {},
v2: boolean = false,
) {
let url = URLs[key];
for (const paramKey of Object.keys(params)) {
url += `/${params[paramKey]}`;
}
return `${v2 ? BASE_URL_API_V2 : getBaseUrl()}${url}`;
return `${getBaseUrl()}${url}`;
}
export type URLsType = typeof URLs;
@@ -24,7 +24,7 @@ import type {
// Mock getURL before importing the module under test
jest.mock("../../../helpers/constants", () => ({
getURL: jest.fn(() => "http://localhost/api/v1/agentic/assist/stream"),
getURL: jest.fn(() => "http://localhost/v1/agentic/assist/stream"),
}));
import { postAssistStream } from "../use-post-assist-stream";
@@ -9,7 +9,7 @@ jest.mock("@/controllers/API/api", () => ({
}));
jest.mock("@/controllers/API/helpers/constants", () => ({
getURL: jest.fn(() => "/api/v1/deployments/providers"),
getURL: jest.fn(() => "/v1/deployments/providers"),
}));
jest.mock("@/controllers/API/services/request-processor", () => ({
@@ -40,7 +40,7 @@ describe("useDeleteProviderAccount", () => {
await mutation.mutate({ provider_id: "prov-1" });
expect(mockApiDelete).toHaveBeenCalledWith(
"/api/v1/deployments/providers/prov-1",
"/v1/deployments/providers/prov-1",
);
});
@@ -19,7 +19,7 @@ jest.mock("@/controllers/API/api", () => ({
}));
jest.mock("@/controllers/API/helpers/constants", () => ({
getURL: jest.fn(() => "/api/v1/deployments/providers"),
getURL: jest.fn(() => "/v1/deployments/providers"),
}));
jest.mock("@/controllers/API/services/request-processor", () => ({
@@ -42,7 +42,7 @@ describe("useGetProviderAccounts", () => {
useGetProviderAccounts({});
await flushPromises();
expect(mockApiGet).toHaveBeenCalledWith("/api/v1/deployments/providers", {
expect(mockApiGet).toHaveBeenCalledWith("/v1/deployments/providers", {
params: { page: 1, size: 20 },
});
});
@@ -55,7 +55,7 @@ describe("useGetProviderAccounts", () => {
useGetProviderAccounts({ page: 2, size: 10 });
await flushPromises();
expect(mockApiGet).toHaveBeenCalledWith("/api/v1/deployments/providers", {
expect(mockApiGet).toHaveBeenCalledWith("/v1/deployments/providers", {
params: { page: 2, size: 10 },
});
});
@@ -9,7 +9,7 @@ jest.mock("@/controllers/API/api", () => ({
}));
jest.mock("@/controllers/API/helpers/constants", () => ({
getURL: jest.fn(() => "/api/v1/deployments/providers"),
getURL: jest.fn(() => "/v1/deployments/providers"),
}));
const mockMutate = jest.fn(
@@ -56,7 +56,7 @@ describe("usePostProviderAccount", () => {
await mutation.mutate(validPayload);
expect(mockApiPost).toHaveBeenCalledWith(
"/api/v1/deployments/providers",
"/v1/deployments/providers",
validPayload,
);
});
@@ -8,7 +8,7 @@ jest.mock("@/controllers/API/api", () => ({
}));
jest.mock("@/controllers/API/helpers/constants", () => ({
getURL: jest.fn(() => "/api/v1/deployments"),
getURL: jest.fn(() => "/v1/deployments"),
}));
jest.mock("@/controllers/API/services/request-processor", () => ({
@@ -47,7 +47,7 @@ describe("useCheckToolNames", () => {
expect(capturedQueryFn).not.toBeNull();
capturedQueryFn!();
expect(mockApiGet).toHaveBeenCalledWith("/api/v1/deployments/snapshots", {
expect(mockApiGet).toHaveBeenCalledWith("/v1/deployments/snapshots", {
params: {
provider_id: "prov-1",
names: ["my_tool", "other_tool"],
@@ -67,7 +67,7 @@ describe("useCheckToolNames", () => {
const calledUrl = mockApiGet.mock.calls[0][0];
expect(calledUrl).not.toContain("check-names");
expect(calledUrl).toBe("/api/v1/deployments/snapshots");
expect(calledUrl).toBe("/v1/deployments/snapshots");
});
// ---------------------------------------------------------------------------
@@ -9,7 +9,7 @@ jest.mock("@/controllers/API/api", () => ({
}));
jest.mock("@/controllers/API/helpers/constants", () => ({
getURL: jest.fn(() => "/api/v1/deployments"),
getURL: jest.fn(() => "/v1/deployments"),
}));
jest.mock("@/controllers/API/services/request-processor", () => ({
@@ -39,7 +39,7 @@ describe("useDeleteDeployment", () => {
const mutation = useDeleteDeployment();
await mutation.mutate({ deployment_id: "dep-1" });
expect(mockApiDelete).toHaveBeenCalledWith("/api/v1/deployments/dep-1");
expect(mockApiDelete).toHaveBeenCalledWith("/v1/deployments/dep-1");
});
it("refetches useGetDeployments on success", async () => {
@@ -19,7 +19,7 @@ jest.mock("@/controllers/API/api", () => ({
}));
jest.mock("@/controllers/API/helpers/constants", () => ({
getURL: jest.fn(() => "/api/v1/deployments"),
getURL: jest.fn(() => "/v1/deployments"),
}));
jest.mock("@/controllers/API/services/request-processor", () => ({
@@ -42,7 +42,7 @@ describe("useGetDeploymentAttachments", () => {
useGetDeploymentAttachments({ deploymentId: "dep-1" });
await flushPromises();
expect(mockApiGet).toHaveBeenCalledWith("/api/v1/deployments/dep-1/flows", {
expect(mockApiGet).toHaveBeenCalledWith("/v1/deployments/dep-1/flows", {
params: { size: 50 },
paramsSerializer: { indexes: null },
});
@@ -19,7 +19,7 @@ jest.mock("@/controllers/API/api", () => ({
}));
jest.mock("@/controllers/API/helpers/constants", () => ({
getURL: jest.fn(() => "/api/v1/deployments"),
getURL: jest.fn(() => "/v1/deployments"),
}));
jest.mock("@/controllers/API/services/request-processor", () => ({
@@ -42,7 +42,7 @@ describe("useGetDeploymentConfigs", () => {
useGetDeploymentConfigs({ providerId: "prov-1" });
await flushPromises();
expect(mockApiGet).toHaveBeenCalledWith("/api/v1/deployments/configs", {
expect(mockApiGet).toHaveBeenCalledWith("/v1/deployments/configs", {
params: { provider_id: "prov-1", size: 10000 },
});
});
@@ -19,7 +19,7 @@ jest.mock("@/controllers/API/api", () => ({
}));
jest.mock("@/controllers/API/helpers/constants", () => ({
getURL: jest.fn(() => "/api/v1/deployments"),
getURL: jest.fn(() => "/v1/deployments"),
}));
jest.mock("@/controllers/API/services/request-processor", () => ({
@@ -42,7 +42,7 @@ describe("useGetDeploymentLlms", () => {
useGetDeploymentLlms({ providerId: "prov-1" });
await flushPromises();
expect(mockApiGet).toHaveBeenCalledWith("/api/v1/deployments/llms", {
expect(mockApiGet).toHaveBeenCalledWith("/v1/deployments/llms", {
params: { provider_id: "prov-1" },
});
});
@@ -5,7 +5,7 @@ jest.mock("@/controllers/API/api", () => ({
}));
jest.mock("@/controllers/API/helpers/constants", () => ({
getURL: jest.fn(() => "/api/v1/deployments"),
getURL: jest.fn(() => "/v1/deployments"),
}));
jest.mock("@/controllers/API/services/request-processor", () => ({
@@ -42,7 +42,7 @@ describe("useGetDeploymentRun", () => {
});
expect(mockApiGet).toHaveBeenCalledWith(
"/api/v1/deployments/dep-1/runs/exec-1",
"/v1/deployments/dep-1/runs/exec-1",
);
});
@@ -58,7 +58,7 @@ describe("useGetDeploymentRun", () => {
});
expect(mockApiGet).toHaveBeenCalledWith(
"/api/v1/deployments/dep-1/runs/exec%2Fwith%20spaces",
"/v1/deployments/dep-1/runs/exec%2Fwith%20spaces",
);
});
@@ -19,7 +19,7 @@ jest.mock("@/controllers/API/api", () => ({
}));
jest.mock("@/controllers/API/helpers/constants", () => ({
getURL: jest.fn(() => "/api/v1/deployments"),
getURL: jest.fn(() => "/v1/deployments"),
}));
jest.mock("@/controllers/API/services/request-processor", () => ({
@@ -40,7 +40,7 @@ describe("useGetDeployment", () => {
useGetDeployment({ deploymentId: "dep-1" });
await flushPromises();
expect(mockApiGet).toHaveBeenCalledWith("/api/v1/deployments/dep-1");
expect(mockApiGet).toHaveBeenCalledWith("/v1/deployments/dep-1");
});
it("uses correct query key", () => {
@@ -5,7 +5,7 @@ jest.mock("@/controllers/API/api", () => ({
}));
jest.mock("@/controllers/API/helpers/constants", () => ({
getURL: jest.fn(() => "/api/v1/deployments"),
getURL: jest.fn(() => "/v1/deployments"),
}));
// Capture the config passed to useQueries so we can test combine and queryFn
@@ -66,7 +66,7 @@ describe("useGetDeploymentsByProviders", () => {
useGetDeploymentsByProviders(["prov-1"]);
await capturedConfig.queries[0].queryFn();
expect(mockApiGet).toHaveBeenCalledWith("/api/v1/deployments", {
expect(mockApiGet).toHaveBeenCalledWith("/v1/deployments", {
params: { provider_id: "prov-1", page: 1, size: 20 },
});
});
@@ -79,7 +79,7 @@ describe("useGetDeploymentsByProviders", () => {
useGetDeploymentsByProviders(["prov-1"], "folder-1");
await capturedConfig.queries[0].queryFn();
expect(mockApiGet).toHaveBeenCalledWith("/api/v1/deployments", {
expect(mockApiGet).toHaveBeenCalledWith("/v1/deployments", {
params: {
provider_id: "prov-1",
project_id: "folder-1",
@@ -21,7 +21,7 @@ jest.mock("@/controllers/API/api", () => ({
}));
jest.mock("@/controllers/API/helpers/constants", () => ({
getURL: jest.fn(() => "/api/v1/deployments"),
getURL: jest.fn(() => "/v1/deployments"),
}));
jest.mock("@/controllers/API/services/request-processor", () => ({
@@ -44,7 +44,7 @@ describe("useGetDeployments", () => {
useGetDeployments({ provider_id: "prov-1" });
await flushPromises();
expect(mockApiGet).toHaveBeenCalledWith("/api/v1/deployments", {
expect(mockApiGet).toHaveBeenCalledWith("/v1/deployments", {
params: {
provider_id: "prov-1",
page: 1,
@@ -67,7 +67,7 @@ describe("useGetDeployments", () => {
});
await flushPromises();
expect(mockApiGet).toHaveBeenCalledWith("/api/v1/deployments", {
expect(mockApiGet).toHaveBeenCalledWith("/v1/deployments", {
params: {
provider_id: "prov-1",
flow_ids: ["f1", "f2"],
@@ -10,7 +10,7 @@ jest.mock("@/controllers/API/api", () => ({
}));
jest.mock("@/controllers/API/helpers/constants", () => ({
getURL: jest.fn(() => "/api/v1/deployments"),
getURL: jest.fn(() => "/v1/deployments"),
}));
jest.mock("@/controllers/API/services/request-processor", () => ({
@@ -49,7 +49,7 @@ describe("usePatchDeployment", () => {
});
expect(mockApiPatch).toHaveBeenCalledWith(
"/api/v1/deployments/dep-42",
"/v1/deployments/dep-42",
expect.any(Object),
);
});
@@ -9,7 +9,7 @@ jest.mock("@/controllers/API/api", () => ({
}));
jest.mock("@/controllers/API/helpers/constants", () => ({
getURL: jest.fn(() => "/api/v1/deployments"),
getURL: jest.fn(() => "/v1/deployments"),
}));
jest.mock("@/controllers/API/services/request-processor", () => ({
@@ -45,7 +45,7 @@ describe("usePatchSnapshot", () => {
});
expect(mockApiPatch).toHaveBeenCalledWith(
"/api/v1/deployments/snapshots/snap-abc",
"/v1/deployments/snapshots/snap-abc",
{ flow_version_id: "fv-1" },
);
});
@@ -5,7 +5,7 @@ jest.mock("@/controllers/API/api", () => ({
}));
jest.mock("@/controllers/API/helpers/constants", () => ({
getURL: jest.fn(() => "/api/v1/deployments"),
getURL: jest.fn(() => "/v1/deployments"),
}));
jest.mock("@/controllers/API/services/request-processor", () => ({
@@ -46,7 +46,7 @@ describe("usePostDeploymentRun", () => {
const mutation = usePostDeploymentRun();
await mutation.mutate(payload);
expect(mockApiPost).toHaveBeenCalledWith("/api/v1/deployments/dep-1/runs", {
expect(mockApiPost).toHaveBeenCalledWith("/v1/deployments/dep-1/runs", {
provider_data: { input: "Hello agent" },
});
});
@@ -9,7 +9,7 @@ jest.mock("@/controllers/API/api", () => ({
}));
jest.mock("@/controllers/API/helpers/constants", () => ({
getURL: jest.fn(() => "/api/v1/deployments"),
getURL: jest.fn(() => "/v1/deployments"),
}));
jest.mock("@/controllers/API/services/request-processor", () => ({
@@ -64,7 +64,7 @@ describe("usePostDeployment", () => {
await mutation.mutate(validPayload);
expect(mockApiPost).toHaveBeenCalledWith(
"/api/v1/deployments",
"/v1/deployments",
validPayload,
);
});
@@ -16,7 +16,7 @@ export const useDeleteFileV2: useMutationFunctionType<IDeleteFile, void> = (
const deleteFileFn = async (): Promise<any> => {
const response = await api.delete<any>(
`${getURL("FILE_MANAGEMENT", { id: params.id }, true)}`,
`${getURL("FILE_MANAGEMENT", { id: params.id })}`,
);
return response.data;
@@ -16,7 +16,7 @@ export const useDeleteFilesV2: useMutationFunctionType<
const deleteFileFn = async (params): Promise<any> => {
const response = await api.delete<any>(
`${getURL("FILE_MANAGEMENT", { mode: "batch/" }, true)}`,
`${getURL("FILE_MANAGEMENT", { mode: "batch/" })}`,
{
data: params.ids,
},
@@ -20,7 +20,7 @@ export const useDuplicateFileV2: useMutationFunctionType<
const duplicateFileFn = async (): Promise<any> => {
// First download the file
const response = await fetch(
`${getURL("FILE_MANAGEMENT", { id: params.id }, true)}`,
`${getURL("FILE_MANAGEMENT", { id: params.id })}`,
{
headers: {
Accept: "*/*",
@@ -40,7 +40,7 @@ export const useDuplicateFileV2: useMutationFunctionType<
formData.append("file", file);
const uploadResponse = await api.post<any>(
`${getURL("FILE_MANAGEMENT", {}, true)}/`,
`${getURL("FILE_MANAGEMENT")}/`,
formData,
);
@@ -19,7 +19,7 @@ export const useGetDownloadFileV2: useMutationFunctionType<
if (!params) return;
// need to use fetch because axios convert blob data to string, and this convertion can corrupt the file
const response = await fetch(
`${getURL("FILE_MANAGEMENT", { id: params.id }, true)}`,
`${getURL("FILE_MANAGEMENT", { id: params.id })}`,
{
headers: {
Accept: "*/*",
@@ -19,7 +19,7 @@ export const useGetDownloadFilesV2: useMutationFunctionType<
let response;
if (params.ids.length === 1) {
response = await fetch(
`${getURL("FILE_MANAGEMENT", { id: params.ids[0] }, true)}`,
`${getURL("FILE_MANAGEMENT", { id: params.ids[0] })}`,
{
headers: {
Accept: "*/*",
@@ -29,7 +29,7 @@ export const useGetDownloadFilesV2: useMutationFunctionType<
);
} else {
response = await fetch(
`${getURL("FILE_MANAGEMENT", { mode: "batch/" }, true)}`,
`${getURL("FILE_MANAGEMENT", { mode: "batch/" })}`,
{
method: "POST",
body: JSON.stringify(params.ids),
@@ -14,7 +14,7 @@ export const useGetFilesV2: useQueryFunctionType<undefined, FilesResponse> = (
const getFilesFn = async () => {
const response = await api.get<FilesResponse>(
`${getURL("FILE_MANAGEMENT", {}, true)}`,
`${getURL("FILE_MANAGEMENT")}`,
);
return response["data"] ?? [];
};
@@ -74,7 +74,7 @@ export const usePostUploadFileV2: useMutationFunctionType<
try {
const response = await api.post<any>(
`${getURL("FILE_MANAGEMENT", {}, true)}`,
`${getURL("FILE_MANAGEMENT")}`,
formData,
{
onUploadProgress: (progressEvent) => {
@@ -17,7 +17,7 @@ export const usePostRenameFileV2: useMutationFunctionType<
const postRenameFileFn = async (payload: IPostRenameFile): Promise<any> => {
const response = await api.put<any>(
`${getURL("FILE_MANAGEMENT", { id: payload.id }, true)}?name=${encodeURI(payload.name)}`,
`${getURL("FILE_MANAGEMENT", { id: payload.id })}?name=${encodeURI(payload.name)}`,
);
return response.data;
@@ -22,7 +22,7 @@ jest.mock("@/controllers/API/api", () => ({
}));
jest.mock("@/controllers/API/helpers/constants", () => ({
getURL: jest.fn((key) => `/api/v1/${key.toLowerCase()}`),
getURL: jest.fn((key) => `/v1/${key.toLowerCase()}`),
}));
jest.mock("@/controllers/API/services/request-processor", () => ({
@@ -14,7 +14,7 @@ jest.mock("@/controllers/API/api", () => ({
}));
jest.mock("@/controllers/API/helpers/constants", () => ({
getURL: jest.fn((key: string) => `/api/v1/${key.toLowerCase()}`),
getURL: jest.fn((key: string) => `/v1/${key.toLowerCase()}`),
}));
jest.mock("@/controllers/API/services/request-processor", () => ({
@@ -40,7 +40,7 @@ export const useAddMCPServer: useMutationFunctionType<
}
const res = await api.post(
`${getURL("MCP_SERVERS", undefined, true)}/${body.name}`,
`${getURL("MCP_SERVERS")}/${body.name}`,
payload,
);
@@ -26,7 +26,7 @@ export const useDeleteMCPServer: useMutationFunctionType<
): Promise<DeleteMCPServerResponse> {
try {
const res = await api.delete(
`${getURL("MCP_SERVERS", undefined, true)}/${payload.name}`,
`${getURL("MCP_SERVERS")}/${payload.name}`,
);
return {
@@ -19,7 +19,7 @@ export const useGetMCPServer: useMutationFunctionType<
const responseFn = async (params: IGetMCPServer) => {
const { data } = await api.get<Omit<getMCPServerResponse, "name">>(
`${getURL("MCP_SERVERS", undefined, true)}/${params.name}`,
`${getURL("MCP_SERVERS")}/${params.name}`,
);
return { ...data, name: params.name };
@@ -22,7 +22,7 @@ export const useGetMCPServers: useQueryFunctionType<
const responseFn = async () => {
try {
const { data } = await api.get<getMCPServersResponse>(
`${getURL("MCP_SERVERS", undefined, true)}?action_count=false`,
`${getURL("MCP_SERVERS")}?action_count=false`,
);
// Merge with cached data to preserve non-null mode/toolsCount
const cachedData = queryClient.getQueryData(["useGetMCPServers"]) as
@@ -56,7 +56,7 @@ export const useGetMCPServers: useQueryFunctionType<
const fetchWithCounts = async () => {
try {
const { data } = await api.get<getMCPServersResponse>(
`${getURL("MCP_SERVERS", undefined, true)}?action_count=true`,
`${getURL("MCP_SERVERS")}?action_count=true`,
);
return data;
} catch (error) {
@@ -41,7 +41,7 @@ export const usePatchMCPServer: useMutationFunctionType<
}
const res = await api.patch(
`${getURL("MCP_SERVERS", undefined, true)}/${body.name}`,
`${getURL("MCP_SERVERS")}/${body.name}`,
payload,
);
@@ -56,7 +56,7 @@ jest.mock("@/controllers/API/api", () => ({
}));
jest.mock("@/controllers/API/helpers/constants", () => ({
getURL: (key: string) => `api/v1/${key.toLowerCase()}`,
getURL: (key: string) => `v1/${key.toLowerCase()}`,
}));
jest.mock("@/utils/utils", () => ({
@@ -101,7 +101,7 @@ describe("useGetMessagesQuery - Routing Logic", () => {
await new Promise((resolve) => setTimeout(resolve, 50));
expect(mockApiGet).toHaveBeenCalledWith(
"api/v1/messages",
"v1/messages",
expect.objectContaining({
params: expect.objectContaining({ flow_id: FLOW_ID }),
}),
@@ -117,7 +117,7 @@ describe("useGetMessagesQuery - Routing Logic", () => {
await new Promise((resolve) => setTimeout(resolve, 50));
expect(mockApiGet).toHaveBeenCalledWith(
"api/v1/messages/shared",
"v1/messages/shared",
expect.objectContaining({ params: { source_flow_id: SOURCE_FLOW_ID } }),
);
});
@@ -28,7 +28,7 @@ jest.mock("@/controllers/API/api", () => ({
}));
jest.mock("@/controllers/API/helpers/constants", () => ({
getURL: (key: string) => `api/v1/${key.toLowerCase()}`,
getURL: (key: string) => `v1/${key.toLowerCase()}`,
}));
jest.mock("@/controllers/API/services/request-processor", () => ({
@@ -67,7 +67,7 @@ describe("useGetSessionsFromFlowQuery - Routing Logic", () => {
await new Promise((resolve) => setTimeout(resolve, 50));
expect(mockApiGet).toHaveBeenCalledWith(
"api/v1/messages/shared/sessions",
"v1/messages/shared/sessions",
expect.objectContaining({ params: { source_flow_id: SOURCE_FLOW_ID } }),
);
});
@@ -16,7 +16,7 @@ export const useGetRegistration: useQueryFunctionType<
const { query } = UseRequestProcessor();
const getRegistration = async (): Promise<RegistrationInfo> => {
const res = await api.get(`${getURL("REGISTRATION", {}, true)}/`);
const res = await api.get(`${getURL("REGISTRATION")}/`);
return res.data;
};
@@ -21,7 +21,7 @@ export const usePostRegistration: useMutationFunctionType<
const postRegistrationFn = async (
payload: IRegistrationRequest,
): Promise<IRegistrationResponse> => {
const response = await api.post(`${getURL("REGISTRATION", {}, true)}/`, {
const response = await api.post(`${getURL("REGISTRATION")}/`, {
email: payload.email,
});
return response.data;
@@ -5,7 +5,7 @@ jest.mock("@/controllers/API/api", () => ({
}));
jest.mock("@/controllers/API/helpers/constants", () => ({
getURL: jest.fn(() => "/api/v1/variables"),
getURL: jest.fn(() => "/v1/variables"),
}));
jest.mock("@/controllers/API/services/request-processor", () => ({
@@ -43,7 +43,7 @@ describe("usePostDetectEnvVars", () => {
await mutation.mutate(payload);
expect(mockApiPost).toHaveBeenCalledWith(
"/api/v1/variables/detections",
"/v1/variables/detections",
payload,
);
});
@@ -1,9 +1,8 @@
export const BASENAME = "";
export const PORT = 3000;
export const PROXY_TARGET = "http://localhost:7860";
export const API_ROUTES = ["^/v1/", "^/v2/", "/health"];
export const API_ROUTES = ["^/v1/", "/health"];
export const BASE_URL_API = "/v1/";
export const BASE_URL_API_V2 = "/v2/";
export const HEALTH_CHECK_URL = "/health_check";
export const DOCS_LINK = "https://docs.hanzo.ai/flow";
@@ -14,6 +13,5 @@ export default {
PROXY_TARGET,
API_ROUTES,
BASE_URL_API,
BASE_URL_API_V2,
HEALTH_CHECK_URL,
};
@@ -17,7 +17,7 @@ describe("customGetMCPUrl", () => {
const url = customGetMCPUrl("proj-1");
expect(url).toBe(
"https://custom.example.com/api/v1/mcp/project/proj-1/streamable",
"https://custom.example.com/v1/mcp/project/proj-1/streamable",
);
});
@@ -28,7 +28,7 @@ describe("customGetMCPUrl", () => {
const url = customGetMCPUrl("proj-1");
expect(url).toBe(
"https://override.example.com/api/v1/mcp/project/proj-1/streamable",
"https://override.example.com/v1/mcp/project/proj-1/streamable",
);
});
@@ -39,7 +39,7 @@ describe("customGetMCPUrl", () => {
const url = customGetMCPUrl("proj-1");
expect(url).toBe(
"https://api-default.example.com/api/v1/mcp/project/proj-1/streamable",
"https://api-default.example.com/v1/mcp/project/proj-1/streamable",
);
});
@@ -50,7 +50,7 @@ describe("customGetMCPUrl", () => {
const url = customGetMCPUrl("proj-1");
expect(url).toBe(
`${window.location.origin}/api/v1/mcp/project/proj-1/streamable`,
`${window.location.origin}/v1/mcp/project/proj-1/streamable`,
);
});
@@ -60,7 +60,7 @@ describe("customGetMCPUrl", () => {
const url = customGetMCPUrl("proj-1");
expect(url).toBe(
"https://example.com/api/v1/mcp/project/proj-1/streamable",
"https://example.com/v1/mcp/project/proj-1/streamable",
);
});
@@ -70,7 +70,7 @@ describe("customGetMCPUrl", () => {
const url = customGetMCPUrl("proj-1");
expect(url).toBe(
"https://example.com/api/v1/mcp/project/proj-1/streamable",
"https://example.com/v1/mcp/project/proj-1/streamable",
);
});
@@ -79,7 +79,7 @@ describe("customGetMCPUrl", () => {
const url = customGetMCPUrl("proj-1", {}, "sse");
expect(url).toBe("https://example.com/api/v1/mcp/project/proj-1/sse");
expect(url).toBe("https://example.com/v1/mcp/project/proj-1/sse");
});
it("returns composer URL when useComposer is true and streamableHttpUrl is set", () => {
@@ -7,7 +7,7 @@ jest.mock("@/controllers/API/api", () => ({
}));
jest.mock("@/controllers/API/helpers/constants", () => ({
getURL: (key: string) => `http://localhost/api/v1/${key.toLowerCase()}`,
getURL: (key: string) => `http://localhost/v1/${key.toLowerCase()}`,
}));
import { useFlowEvents } from "../use-flow-events";
@@ -156,7 +156,7 @@ describe("API Snippet Generation Utilities", () => {
// Check for file upload steps
expect(code).toContain("/v1/files/upload/");
expect(code).toContain("/v2/files");
expect(code).toContain("/v1/files");
expect(code).toContain("with open");
expect(code).toContain('files={"file": f}');
});
@@ -180,7 +180,7 @@ describe("API Snippet Generation Utilities", () => {
// Check for file upload steps
expect(code).toContain("/v1/files/upload/");
expect(code).toContain("/v2/files");
expect(code).toContain("/v1/files");
expect(code).toContain("with open");
expect(code).toContain('files={"file": f}');
});
@@ -245,7 +245,7 @@ describe("API Snippet Generation Utilities", () => {
// Check for upload steps
expect(code).toContain("/v1/files/upload/");
expect(code).toContain("/v2/files");
expect(code).toContain("/v1/files");
});
it("should generate multi-step code for file uploads without authentication", () => {
@@ -277,7 +277,7 @@ describe("API Snippet Generation Utilities", () => {
// Check for upload steps
expect(code).toContain("/v1/files/upload/");
expect(code).toContain("/v2/files");
expect(code).toContain("/v1/files");
});
});
@@ -364,7 +364,7 @@ describe("API Snippet Generation Utilities", () => {
expect(result.steps[0].title).toContain("Upload files");
expect(result.steps[0]).toHaveProperty("code");
expect(result.steps[0].code).toContain("/v1/files/upload/");
expect(result.steps[0].code).toContain("/v2/files");
expect(result.steps[0].code).toContain("/v1/files");
expect(result.steps[0].code).toContain('--form "file=@');
expect(result.steps[0].code).toContain("x-api-key: YOUR_API_KEY_HERE");
@@ -403,7 +403,7 @@ describe("API Snippet Generation Utilities", () => {
expect(result.steps[0].title).toContain("Upload files");
expect(result.steps[0]).toHaveProperty("code");
expect(result.steps[0].code).toContain("/v1/files/upload/");
expect(result.steps[0].code).toContain("/v2/files");
expect(result.steps[0].code).toContain("/v1/files");
expect(result.steps[0].code).toContain('--form "file=@');
expect(result.steps[0].code).not.toContain(
"x-api-key: YOUR_API_KEY_HERE",
@@ -312,7 +312,7 @@ describe("getNewCurlCode", () => {
expect(result.steps).toHaveLength(2);
// Check upload step
expect(result.steps[0].code).toContain("/v2/files");
expect(result.steps[0].code).toContain("/v1/files");
expect(result.steps[0].code).toContain('--form "file=@your_file_1.pdf"');
// Check execute step
@@ -336,7 +336,7 @@ describe("getNewCurlCode", () => {
expect(result.steps).toHaveLength(2);
// Check upload step
expect(result.steps[0].code).toContain("/v2/files");
expect(result.steps[0].code).toContain("/v1/files");
expect(result.steps[0].code).toContain('--form "file=@your_file_1.pdf"');
});
@@ -358,7 +358,7 @@ describe("getNewCurlCode", () => {
// Check both uploads
expect(result.steps[0].code).toContain("your_file_1.pdf");
expect(result.steps[0].code).toContain("your_file_2.pdf");
expect(result.steps[0].code).toContain("/v2/files");
expect(result.steps[0].code).toContain("/v1/files");
});
});
@@ -405,7 +405,7 @@ describe("getNewCurlCode", () => {
// Check upload step
expect(result.steps[0].code).toContain("curl.exe --request POST");
expect(result.steps[0].code).toContain("/v2/files");
expect(result.steps[0].code).toContain("/v1/files");
});
});
@@ -428,7 +428,7 @@ describe("getNewCurlCode", () => {
// Check for both v1 and v2 API uploads
expect(result.steps[0].code).toContain("/v1/files/upload/");
expect(result.steps[0].code).toContain("/v2/files");
expect(result.steps[0].code).toContain("/v1/files");
// Check all upload counter placeholders
expect(result.steps[1].code).toContain(
@@ -159,7 +159,7 @@ describe("getNewJsApiCode", () => {
"// Step 1: Upload file for File/VideoFile fileNode1",
);
expect(code).toContain("createFormData('your_file_1.pdf')");
expect(code).toContain("/v2/files");
expect(code).toContain("/v1/files");
expect(code).toContain("filePath1");
});
@@ -179,7 +179,7 @@ describe("getNewJsApiCode", () => {
"// Step 1: Upload file for File/VideoFile videoNode1",
);
expect(code).toContain("createFormData('your_file_1.pdf')");
expect(code).toContain("/v2/files");
expect(code).toContain("/v1/files");
expect(code).toContain("filePath1");
});
@@ -232,7 +232,7 @@ describe("getNewJsApiCode", () => {
expect(code).toContain(
"// Step 3: Upload file for File/VideoFile videoNode1",
);
expect(code).toContain("/v2/files");
expect(code).toContain("/v1/files");
// Check for flow execution step
expect(code).toContain("// Step 4: Execute flow with all file paths");
@@ -185,7 +185,7 @@ describe("getNewPythonApiCode", () => {
"# Step 1: Upload file for File/VideoFile fileNode1",
);
expect(code).toContain('with open("your_file_1.pdf", "rb") as f:');
expect(code).toContain('f"{base_url}/v2/files"');
expect(code).toContain('f"{base_url}/v1/files"');
expect(code).toContain('file_path_1 = response.json()["path"]');
// Check for flow execution step
@@ -209,7 +209,7 @@ describe("getNewPythonApiCode", () => {
"# Step 1: Upload file for File/VideoFile videoNode1",
);
expect(code).toContain('with open("your_file_1.pdf", "rb") as f:');
expect(code).toContain('f"{base_url}/v2/files"');
expect(code).toContain('f"{base_url}/v1/files"');
expect(code).toContain('file_path_1 = response.json()["path"]');
// Check for flow execution step
@@ -268,7 +268,7 @@ describe("getNewPythonApiCode", () => {
expect(code).toContain(
"# Step 3: Upload file for File/VideoFile videoNode1",
);
expect(code).toContain("/v2/files");
expect(code).toContain("/v1/files");
// Check for flow execution step
expect(code).toContain("# Step 4: Execute flow with all file paths");
@@ -169,14 +169,14 @@ ${getApiSampleHeaders("curl")}
if (detectedPlatform === "powershell") {
uploadCommands.push(
`curl.exe --request POST \`
--url "${baseUrl}/v2/files" \`
--url "${baseUrl}/v1/files" \`
${shouldDisplayApiKey ? '--header "x-api-key: YOUR_API_KEY_HERE" \\' : getApiSampleHeaders("curl")}
--form "file=@your_file_${uploadCounter}.pdf"`,
);
} else {
uploadCommands.push(
`curl --request POST \\
--url "${baseUrl}/v2/files" \\
--url "${baseUrl}/v1/files" \\
${shouldDisplayApiKey ? '--header "x-api-key: YOUR_API_KEY_HERE" \\' : getApiSampleHeaders("curl")}
--form "file=@your_file_${uploadCounter}.pdf"`,
);
@@ -154,7 +154,7 @@ const authHeaders = { 'x-api-key': apiKey };`
const fileUploadOptions${index + 1} = {
hostname: '${hostname}',
port: ${port},
path: '/v2/files',
path: '/v1/files',
method: 'POST',
headers: {
'Content-Type': \`multipart/form-data; boundary=\${fileBoundary${
@@ -125,7 +125,7 @@ except ValueError as e:
uploadSteps.length + 1
}: Upload file for File/VideoFile ${nodeId}\nwith open(\"your_file_${
index + 1
}.pdf\", \"rb\") as f:\n response = requests.post(\n f\"{base_url}/v2/files\",\n headers=headers,\n files={\"file\": f}\n )\n response.raise_for_status()\n file_path_${
}.pdf\", \"rb\") as f:\n response = requests.post(\n f\"{base_url}/v1/files\",\n headers=headers,\n files={\"file\": f}\n )\n response.raise_for_status()\n file_path_${
index + 1
} = response.json()[\"path\"]`,
);
@@ -80,7 +80,7 @@ def get_llm(
# ``get_language_model_options``, does not inject ``model_class`` into
# each model's metadata). This happens for example immediately after a
# user configures a provider and the frontend augments its dropdown from
# ``/api/v1/models`` before the backend has repopulated
# ``/v1/models`` before the backend has repopulated
# ``template[model]["options"]``; the resulting stored selection only
# carries the raw ``create_model_metadata`` fields, so we have to derive
# ``model_class`` from the provider mapping that
+1 -1
View File
@@ -1,6 +1,6 @@
"""lfx push -- push normalized flow JSON to a remote Flow instance.
Uses stable flow IDs for upsert (PUT /api/v1/flows/{id}), so repeated pushes
Uses stable flow IDs for upsert (PUT /v1/flows/{id}), so repeated pushes
are idempotent: the first push creates the flow, subsequent ones update it in
place without changing its ID on the remote instance.
+1 -1
View File
@@ -63,7 +63,7 @@ class FlowClient:
return headers
def _url(self, path: str) -> str:
return f"{self.server_url}/api/v1{path}"
return f"{self.server_url}/v1{path}"
async def get(self, path: str, **kwargs: Any) -> Any:
url = self._url(path)
+1 -1
View File
@@ -1,6 +1,6 @@
"""Component registry cache — fetches and searches component types.
Loads the full component catalog from /api/v1/all and provides
Loads the full component catalog from /v1/all and provides
pure search/describe functions.
"""
+2 -2
View File
@@ -130,8 +130,8 @@ class WorkflowJobResponse(BaseModel):
"""Automatically populate links for the client."""
if not self.links:
self.links = {
"status": f"/v2/workflows?job_id={self.job_id!s}",
"stop": "/v2/workflows/stop",
"status": f"/v1/workflows?job_id={self.job_id!s}",
"stop": "/v1/workflows/stop",
}
return self
+15 -15
View File
@@ -130,7 +130,7 @@ class AsyncFlowClient(_ClientCommon):
) -> list[Flow]:
resp = await self._request(
"GET",
"/api/v1/flows/",
"/v1/flows/",
params=self._build_flow_list_params(
folder_id=folder_id,
remove_example_flows=remove_example_flows,
@@ -144,17 +144,17 @@ class AsyncFlowClient(_ClientCommon):
return self._validate_model_list(Flow, resp.json())
async def get_flow(self, flow_id: UUID | str) -> Flow:
resp = await self._request("GET", f"/api/v1/flows/{flow_id}")
resp = await self._request("GET", f"/v1/flows/{flow_id}")
return self._validate_model(Flow, resp.json())
async def create_flow(self, flow: FlowCreate) -> Flow:
resp = await self._request("POST", "/api/v1/flows/", json=self._model_payload(flow))
resp = await self._request("POST", "/v1/flows/", json=self._model_payload(flow))
return self._validate_model(Flow, resp.json())
async def update_flow(self, flow_id: UUID | str, update: FlowUpdate) -> Flow:
resp = await self._request(
"PATCH",
f"/api/v1/flows/{flow_id}",
f"/v1/flows/{flow_id}",
json=self._model_payload(update),
)
return self._validate_model(Flow, resp.json())
@@ -163,13 +163,13 @@ class AsyncFlowClient(_ClientCommon):
"""Create-or-update by stable ID. Returns ``(flow, created)``."""
resp = await self._request(
"PUT",
f"/api/v1/flows/{flow_id}",
f"/v1/flows/{flow_id}",
json=self._model_payload(flow),
)
return self._upsert_result(Flow, resp)
async def delete_flow(self, flow_id: UUID | str) -> None:
await self._request("DELETE", f"/api/v1/flows/{flow_id}")
await self._request("DELETE", f"/v1/flows/{flow_id}")
async def run_flow(
self,
@@ -178,7 +178,7 @@ class AsyncFlowClient(_ClientCommon):
) -> RunResponse:
resp = await self._request(
"POST",
f"/api/v1/run/{flow_id_or_endpoint}",
f"/v1/run/{flow_id_or_endpoint}",
json=self._model_payload(request),
)
return self._validate_model(RunResponse, resp.json())
@@ -275,7 +275,7 @@ class AsyncFlowClient(_ClientCommon):
response = chunk.final_response()
"""
return self._aiter_stream(
f"/api/v1/run/{flow_id_or_endpoint}",
f"/v1/run/{flow_id_or_endpoint}",
self._build_stream_payload(
input_value=input_value,
input_type=input_type,
@@ -306,27 +306,27 @@ class AsyncFlowClient(_ClientCommon):
# ------------------------------------------------------------------
async def list_projects(self) -> list[Project]:
resp = await self._request("GET", "/api/v1/projects/")
resp = await self._request("GET", "/v1/projects/")
return self._validate_model_list(Project, resp.json())
async def get_project(self, project_id: UUID | str) -> ProjectWithFlows:
resp = await self._request("GET", f"/api/v1/projects/{project_id}")
resp = await self._request("GET", f"/v1/projects/{project_id}")
return self._validate_model(ProjectWithFlows, resp.json())
async def create_project(self, project: ProjectCreate) -> Project:
resp = await self._request("POST", "/api/v1/projects/", json=self._model_payload(project))
resp = await self._request("POST", "/v1/projects/", json=self._model_payload(project))
return self._validate_model(Project, resp.json())
async def update_project(self, project_id: UUID | str, update: ProjectUpdate) -> Project:
resp = await self._request(
"PATCH",
f"/api/v1/projects/{project_id}",
f"/v1/projects/{project_id}",
json=self._model_payload(update),
)
return self._validate_model(Project, resp.json())
async def delete_project(self, project_id: UUID | str) -> None:
await self._request("DELETE", f"/api/v1/projects/{project_id}")
await self._request("DELETE", f"/v1/projects/{project_id}")
async def download_project(self, project_id: UUID | str) -> dict[str, bytes]:
"""Download all flows in a project.
@@ -334,13 +334,13 @@ class AsyncFlowClient(_ClientCommon):
Raises :class:`ValueError` if the archive contains more than 500
entries or any single entry exceeds 50 MB (zip-bomb protection).
"""
resp = await self._request("GET", f"/api/v1/projects/download/{project_id}")
resp = await self._request("GET", f"/v1/projects/download/{project_id}")
return self._extract_project_archive(resp.content)
async def upload_project(self, zip_bytes: bytes) -> list[Flow]:
resp = await self._request(
"POST",
"/api/v1/projects/upload/",
"/v1/projects/upload/",
content=zip_bytes,
headers={"Content-Type": "application/octet-stream"},
)
+1 -1
View File
@@ -3,7 +3,7 @@
Wraps an :class:`asyncio.Task` so callers can start a flow run and poll or
await it without blocking the event loop. Mirrors the ``BackgroundJob`` API
from flow-ai/sdk PR #1 (Janardan Singh Kavia, IBM Corp., Apache 2.0)
adapted for the Flow V1 ``/api/v1/run/{id}`` endpoint.
adapted for the Flow V1 ``/v1/run/{id}`` endpoint.
Typical usage::
+16 -16
View File
@@ -140,7 +140,7 @@ class FlowClient(_ClientCommon):
) -> list[Flow]:
resp = self._request(
"GET",
"/api/v1/flows/",
"/v1/flows/",
params=self._build_flow_list_params(
folder_id=folder_id,
remove_example_flows=remove_example_flows,
@@ -154,17 +154,17 @@ class FlowClient(_ClientCommon):
return self._validate_model_list(Flow, resp.json())
def get_flow(self, flow_id: UUID | str) -> Flow:
resp = self._request("GET", f"/api/v1/flows/{flow_id}")
resp = self._request("GET", f"/v1/flows/{flow_id}")
return self._validate_model(Flow, resp.json())
def create_flow(self, flow: FlowCreate) -> Flow:
resp = self._request("POST", "/api/v1/flows/", json=self._model_payload(flow))
resp = self._request("POST", "/v1/flows/", json=self._model_payload(flow))
return self._validate_model(Flow, resp.json())
def update_flow(self, flow_id: UUID | str, update: FlowUpdate) -> Flow:
resp = self._request(
"PATCH",
f"/api/v1/flows/{flow_id}",
f"/v1/flows/{flow_id}",
json=self._model_payload(update),
)
return self._validate_model(Flow, resp.json())
@@ -177,13 +177,13 @@ class FlowClient(_ClientCommon):
"""
resp = self._request(
"PUT",
f"/api/v1/flows/{flow_id}",
f"/v1/flows/{flow_id}",
json=self._model_payload(flow),
)
return self._upsert_result(Flow, resp)
def delete_flow(self, flow_id: UUID | str) -> None:
self._request("DELETE", f"/api/v1/flows/{flow_id}")
self._request("DELETE", f"/v1/flows/{flow_id}")
def run_flow(
self,
@@ -192,7 +192,7 @@ class FlowClient(_ClientCommon):
) -> RunResponse:
resp = self._request(
"POST",
f"/api/v1/run/{flow_id_or_endpoint}",
f"/v1/run/{flow_id_or_endpoint}",
json=self._model_payload(request),
)
return self._validate_model(RunResponse, resp.json())
@@ -244,7 +244,7 @@ class FlowClient(_ClientCommon):
response = chunk.final_response()
"""
return self._iter_stream(
f"/api/v1/run/{flow_id_or_endpoint}",
f"/v1/run/{flow_id_or_endpoint}",
self._build_stream_payload(
input_value=input_value,
input_type=input_type,
@@ -275,27 +275,27 @@ class FlowClient(_ClientCommon):
# ------------------------------------------------------------------
def list_projects(self) -> list[Project]:
resp = self._request("GET", "/api/v1/projects/")
resp = self._request("GET", "/v1/projects/")
return self._validate_model_list(Project, resp.json())
def get_project(self, project_id: UUID | str) -> ProjectWithFlows:
resp = self._request("GET", f"/api/v1/projects/{project_id}")
resp = self._request("GET", f"/v1/projects/{project_id}")
return self._validate_model(ProjectWithFlows, resp.json())
def create_project(self, project: ProjectCreate) -> Project:
resp = self._request("POST", "/api/v1/projects/", json=self._model_payload(project))
resp = self._request("POST", "/v1/projects/", json=self._model_payload(project))
return self._validate_model(Project, resp.json())
def update_project(self, project_id: UUID | str, update: ProjectUpdate) -> Project:
resp = self._request(
"PATCH",
f"/api/v1/projects/{project_id}",
f"/v1/projects/{project_id}",
json=self._model_payload(update),
)
return self._validate_model(Project, resp.json())
def delete_project(self, project_id: UUID | str) -> None:
self._request("DELETE", f"/api/v1/projects/{project_id}")
self._request("DELETE", f"/v1/projects/{project_id}")
def download_project(self, project_id: UUID | str) -> dict[str, bytes]:
"""Download all flows in a project.
@@ -306,14 +306,14 @@ class FlowClient(_ClientCommon):
Raises :class:`ValueError` if the archive contains more than 500
entries or any single entry exceeds 50 MB (zip-bomb protection).
"""
resp = self._request("GET", f"/api/v1/projects/download/{project_id}")
resp = self._request("GET", f"/v1/projects/download/{project_id}")
return self._extract_project_archive(resp.content)
def upload_project(self, zip_bytes: bytes) -> list[Flow]:
"""Upload a project ZIP archive and return the created flows."""
resp = self._request(
"POST",
"/api/v1/projects/upload/",
"/v1/projects/upload/",
content=zip_bytes,
headers={"Content-Type": "application/octet-stream"},
)
@@ -327,7 +327,7 @@ class FlowClient(_ClientCommon):
"""Upload or update a flow from a local JSON file.
The ``id`` field embedded in the file is used for upsert
(create-or-update via ``PUT /api/v1/flows/{id}``).
(create-or-update via ``PUT /v1/flows/{id}``).
Returns ``(flow, created)`` where ``created`` is ``True`` when the
flow was newly created and ``False`` when it was updated::