Compare commits

..

51 Commits

Author SHA1 Message Date
Mauricio Siu
2d2a3d74ec Merge pull request #2412 from moosti/feat/two-factor-autofocus
feat: add autofocus to two-factor authentication input
2025-08-24 13:10:30 -06:00
Mauricio Siu
56b9fb531a Merge pull request #2447 from divaltor/volume-backup
feat(volume): Add possibility to keep latest N backups for custom apps
2025-08-24 00:44:27 -06:00
Mauricio Siu
59aaa1a47a fix(ui): adjust max width for volume backup dialog based on backup type 2025-08-24 00:40:17 -06:00
autofix-ci[bot]
5e4444610c [autofix.ci] apply automated fixes 2025-08-24 06:33:36 +00:00
Mauricio Siu
34e6cd87df Merge pull request #2410 from gentslava/fix/ollama-ai-provider
Ollama AI provider
2025-08-24 00:30:59 -06:00
Mauricio Siu
31b13b8d34 Merge pull request #2453 from Dokploy/2452-no-removal-of-preview-deployments-when-they-are-merged
fix: correct application not found error message and improve error ha…
2025-08-23 23:01:03 -06:00
Mauricio Siu
746cf76cf3 fix: correct application not found error message and improve error handling in removePreviewDeployment function 2025-08-23 22:59:52 -06:00
Mauricio Siu
46c53a05bf Merge pull request #2231 from PiquelChips/feat/label-previews
feat: preview deployments for pull requests with specific labels
2025-08-23 20:19:50 -06:00
Mauricio Siu
f97f6d8178 Merge branch 'feat/label-previews' of github.com:PiquelChips/dokploy into feat/label-previews 2025-08-23 20:19:34 -06:00
Mauricio Siu
c653dd604f feat: add previewLabels property to baseApp in drop and traefik test files 2025-08-23 20:19:14 -06:00
autofix-ci[bot]
40877e4370 [autofix.ci] apply automated fixes 2025-08-24 02:16:35 +00:00
Mauricio Siu
65203036f2 Merge branch 'canary' into feat/label-previews 2025-08-23 20:15:37 -06:00
Mauricio Siu
2ef5f967a9 refactor: clean up imports in show-preview-settings component 2025-08-23 20:14:41 -06:00
Mauricio Siu
b20c95ffbc Merge branch 'canary' into feat/label-previews 2025-08-23 20:14:16 -06:00
Mauricio Siu
09b2492585 Merge branch 'feat/label-previews' of github.com:PiquelChips/dokploy into feat/label-previews 2025-08-23 20:13:22 -06:00
Mauricio Siu
ca1fa7c4f7 feat: add support for preview labels in deployment process 2025-08-23 20:11:18 -06:00
autofix-ci[bot]
112b898d98 [autofix.ci] apply automated fixes 2025-08-24 02:01:00 +00:00
Mauricio Siu
8185482bcd Merge pull request #2370 from gentslava/fix/traefik_3
bump: Traefik 3.5.0
2025-08-23 19:53:47 -06:00
Mauricio Siu
dd8f5dba09 Merge branch 'canary' into fix/traefik_3 2025-08-23 19:53:40 -06:00
Mauricio Siu
e72a468c7e Merge pull request #2111 from Marukome0743/traefik
feat: bump Traefik v3.2.2 and add swarm network label
2025-08-23 19:50:50 -06:00
Mauricio Siu
02dd793dfb Merge pull request #2396 from alexevladgabriel/feat/self-env-refs
feat: Self reference env variables
2025-08-23 19:38:34 -06:00
Mauricio Siu
64ef033950 Merge pull request #2418 from periakteon/canary
fix(organization): integrate active organization refetching on update/create
2025-08-23 19:32:45 -06:00
Mauricio Siu
32f7bdf398 Merge pull request #2450 from Dokploy/2403-no-delete-volumes-option-when-deleting-in-bulk
feat(ui): add bulk deploy functionality for services in project dashb…
2025-08-23 16:59:03 -06:00
Mauricio Siu
8d73b77a19 Merge branch 'canary' into 2403-no-delete-volumes-option-when-deleting-in-bulk 2025-08-23 16:08:15 -06:00
Mauricio Siu
2e3d4f1021 feat(ui): implement bulk delete dialog for services in project dashboard 2025-08-23 16:06:25 -06:00
Mauricio Siu
ba1f4dbd3a feat(ui): add bulk deploy functionality for services in project dashboard 2025-08-23 16:04:13 -06:00
Mauricio Siu
653beac3d9 feat(ui): implement bulk delete dialog for services with volume deletion option 2025-08-23 15:55:56 -06:00
Vlad Vladov
37c34fdadc feat(volume): Add possibility to keep latest N backups for custom containers 2025-08-23 18:07:45 +03:00
Masum Gökyüz
69d676178f feat(organization): integrate active organization refetching on update/create 2025-08-20 09:33:01 +03:00
Vyacheslav Scherbinin
6612c92b4f chore: update ai providers 2025-08-20 13:16:04 +07:00
Vyacheslav Scherbinin
88c8fe4614 chore: update ollama ai provider 2025-08-20 00:58:39 +07:00
Vyacheslav Scherbinin
623fc26de5 fix(ai-ui): hide api key field for ollama 2025-08-19 23:56:54 +07:00
Vyacheslav Scherbinin
220576fd63 fix(ai-ui): empty models list text 2025-08-19 23:56:54 +07:00
Vyacheslav Scherbinin
07c23292da fix(ai): ollama fetch models 2025-08-19 23:56:54 +07:00
Vyacheslav Scherbinin
72fca80047 fix(ai-ui): disable AI key autocomplete 2025-08-19 23:56:54 +07:00
Vyacheslav Scherbinin
1e7f614bb6 fix(ai): ollama provider url-based detection 2025-08-19 23:56:53 +07:00
Vyacheslav Scherbinin
e2662a0ec5 fix(ai): ollama ai provider api url 2025-08-19 23:56:46 +07:00
ispareh
c96c25ca9f feat: add autofocus to two-factor authentication input 2025-08-19 14:40:04 +03:30
Marukome0743
4afd2d11fa feat: bump traefik to v3.2.2 2025-08-19 18:57:03 +09:00
Scai
8cc054389a feat: add self reference for env variables 2025-08-18 02:04:23 +03:00
autofix-ci[bot]
2c591cbd03 [autofix.ci] apply automated fixes 2025-08-13 01:25:30 +00:00
Vyacheslav Scherbinin
3864c50deb bump: Traefik v3.5.0 2025-08-13 08:23:30 +07:00
PiquelChips
15e62961e8 fix: would only create previews if none of the labels were present 2025-08-11 14:09:02 +02:00
PiquelChips
429c1e4cd8 feat: better UI for submitting labels 2025-08-11 14:03:30 +02:00
Piquel
1904a3d1e9 Merge branch 'canary' into feat/label-previews 2025-08-11 13:29:04 +02:00
Mauricio Siu
025d439f71 Merge branch 'canary' into feat/label-previews 2025-08-02 00:28:52 -06:00
autofix-ci[bot]
9baafb83ff [autofix.ci] apply automated fixes 2025-07-28 07:38:28 +00:00
PiquelChips
1f9ef473f1 format some files 2025-07-24 19:45:43 +02:00
PiquelChips
a0bbf7be23 add check for presence of labels 2025-07-24 19:35:33 +02:00
PiquelChips
a5bc384d77 run database migration 2025-07-24 19:02:50 +02:00
PiquelChips
f2ae39aa86 feat: preview deployments for pull requests with specific labels 2025-07-23 21:39:54 +02:00
26 changed files with 7164 additions and 305 deletions

View File

@@ -27,6 +27,7 @@ if (typeof window === "undefined") {
const baseApp: ApplicationNested = {
railpackVersion: "0.2.2",
applicationId: "",
previewLabels: [],
herokuVersion: "",
giteaBranch: "",
giteaBuildPath: "",

View File

@@ -177,3 +177,77 @@ COMPLEX_VAR="'Prefix \"DoubleQuoted\" and \${{project.APP_NAME}}'"
]);
});
});
describe("prepareEnvironmentVariables (self references)", () => {
it("resolves self references correctly", () => {
const serviceEnv = `
ENVIRONMENT=staging
DATABASE_URL=postgres://postgres:postgres@localhost:5432/project_db
SELF_REF=\${{ENVIRONMENT}}
`;
const resolved = prepareEnvironmentVariables(serviceEnv, "");
expect(resolved).toEqual([
"ENVIRONMENT=staging",
"DATABASE_URL=postgres://postgres:postgres@localhost:5432/project_db",
"SELF_REF=staging",
]);
});
it("throws on undefined self references", () => {
const serviceEnv = `
MISSING_VAR=\${{UNDEFINED_VAR}}
`;
expect(() => prepareEnvironmentVariables(serviceEnv, "")).toThrow(
"Invalid service environment variable: UNDEFINED_VAR",
);
});
it("allows overriding and still resolving from self", () => {
const serviceEnv = `
ENVIRONMENT=production
OVERRIDE_ENV=\${{ENVIRONMENT}}
`;
const resolved = prepareEnvironmentVariables(serviceEnv, "");
expect(resolved).toEqual([
"ENVIRONMENT=production",
"OVERRIDE_ENV=production",
]);
});
it("resolves multiple self references inside one value", () => {
const serviceEnv = `
ENVIRONMENT=staging
APP_NAME=MyApp
COMPLEX=\${{APP_NAME}}-\${{ENVIRONMENT}}-\${{APP_NAME}}
`;
const resolved = prepareEnvironmentVariables(serviceEnv, "");
expect(resolved).toEqual([
"ENVIRONMENT=staging",
"APP_NAME=MyApp",
"COMPLEX=MyApp-staging-MyApp",
]);
});
it("handles quotes with self references", () => {
const serviceEnv = `
ENVIRONMENT=production
QUOTED="'\${{ENVIRONMENT}}'"
MIXED="\"Double \${{ENVIRONMENT}}\""
`;
const resolved = prepareEnvironmentVariables(serviceEnv, "");
expect(resolved).toEqual([
"ENVIRONMENT=production",
"QUOTED='production'",
'MIXED="Double production"',
]);
});
});

View File

@@ -6,6 +6,7 @@ const baseApp: ApplicationNested = {
railpackVersion: "0.2.2",
rollbackActive: false,
applicationId: "",
previewLabels: [],
herokuVersion: "",
giteaRepository: "",
giteaOwner: "",

View File

@@ -1,9 +1,10 @@
import { zodResolver } from "@hookform/resolvers/zod";
import { Settings2 } from "lucide-react";
import { HelpCircle, Plus, Settings2, X } from "lucide-react";
import { useEffect, useState } from "react";
import { useForm } from "react-hook-form";
import { toast } from "sonner";
import { z } from "zod";
import { Badge } from "@/components/ui/badge";
import { Button } from "@/components/ui/button";
import {
Dialog,
@@ -33,6 +34,12 @@ import {
SelectValue,
} from "@/components/ui/select";
import { Switch } from "@/components/ui/switch";
import {
Tooltip,
TooltipContent,
TooltipProvider,
TooltipTrigger,
} from "@/components/ui/tooltip";
import { api } from "@/utils/api";
const schema = z
@@ -42,6 +49,7 @@ const schema = z
wildcardDomain: z.string(),
port: z.number(),
previewLimit: z.number(),
previewLabels: z.array(z.string()).optional(),
previewHttps: z.boolean(),
previewPath: z.string(),
previewCertificateType: z.enum(["letsencrypt", "none", "custom"]),
@@ -81,6 +89,7 @@ export const ShowPreviewSettings = ({ applicationId }: Props) => {
wildcardDomain: "*.traefik.me",
port: 3000,
previewLimit: 3,
previewLabels: [],
previewHttps: false,
previewPath: "/",
previewCertificateType: "none",
@@ -102,6 +111,7 @@ export const ShowPreviewSettings = ({ applicationId }: Props) => {
buildArgs: data.previewBuildArgs || "",
wildcardDomain: data.previewWildcard || "*.traefik.me",
port: data.previewPort || 3000,
previewLabels: data.previewLabels || [],
previewLimit: data.previewLimit || 3,
previewHttps: data.previewHttps || false,
previewPath: data.previewPath || "/",
@@ -119,6 +129,7 @@ export const ShowPreviewSettings = ({ applicationId }: Props) => {
previewBuildArgs: formData.buildArgs,
previewWildcard: formData.wildcardDomain,
previewPort: formData.port,
previewLabels: formData.previewLabels,
applicationId,
previewLimit: formData.previewLimit,
previewHttps: formData.previewHttps,
@@ -200,6 +211,90 @@ export const ShowPreviewSettings = ({ applicationId }: Props) => {
</FormItem>
)}
/>
<FormField
control={form.control}
name="previewLabels"
render={({ field }) => (
<FormItem className="md:col-span-2">
<div className="flex items-center gap-2">
<FormLabel>Preview Labels</FormLabel>
<TooltipProvider>
<Tooltip>
<TooltipTrigger asChild>
<HelpCircle className="size-4 text-muted-foreground hover:text-foreground transition-colors cursor-pointer" />
</TooltipTrigger>
<TooltipContent>
<p>
Add a labels that will trigger a preview
deployment for a pull request. If no labels
are specified, all pull requests will trigger
a preview deployment.
</p>
</TooltipContent>
</Tooltip>
</TooltipProvider>
</div>
<div className="flex flex-wrap gap-2 mb-2">
{field.value?.map((label, index) => (
<Badge
key={index}
variant="secondary"
className="flex items-center gap-1"
>
{label}
<X
className="size-3 cursor-pointer hover:text-destructive"
onClick={() => {
const newLabels = [...(field.value || [])];
newLabels.splice(index, 1);
field.onChange(newLabels);
}}
/>
</Badge>
))}
</div>
<div className="flex gap-2">
<FormControl>
<Input
placeholder="Enter a label (e.g. enhancements, needs-review)"
onKeyDown={(e) => {
if (e.key === "Enter") {
e.preventDefault();
const input = e.currentTarget;
const label = input.value.trim();
if (label) {
field.onChange([
...(field.value || []),
label,
]);
input.value = "";
}
}
}}
/>
</FormControl>
<Button
type="button"
variant="outline"
size="icon"
onClick={() => {
const input = document.querySelector(
'input[placeholder*="Enter a label"]',
) as HTMLInputElement;
const label = input.value.trim();
if (label) {
field.onChange([...(field.value || []), label]);
input.value = "";
}
}}
>
<Plus className="size-4" />
</Button>
</div>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="previewLimit"

View File

@@ -55,7 +55,12 @@ const formSchema = z
cronExpression: z.string().min(1, "Cron expression is required"),
volumeName: z.string().min(1, "Volume name is required"),
prefix: z.string(),
// keepLatestCount: z.coerce.number().optional(),
keepLatestCount: z.coerce
.number()
.int()
.gte(1, "Must be at least 1")
.optional()
.nullable(),
turnOff: z.boolean().default(false),
enabled: z.boolean().default(true),
serviceType: z.enum([
@@ -108,6 +113,7 @@ export const HandleVolumeBackups = ({
}: Props) => {
const [isOpen, setIsOpen] = useState(false);
const [cacheType, setCacheType] = useState<CacheType>("cache");
const [keepLatestCountInput, setKeepLatestCountInput] = useState("");
const utils = api.useUtils();
const form = useForm<z.infer<typeof formSchema>>({
@@ -117,7 +123,7 @@ export const HandleVolumeBackups = ({
cronExpression: "",
volumeName: "",
prefix: "",
// keepLatestCount: undefined,
keepLatestCount: undefined,
turnOff: false,
enabled: true,
serviceName: "",
@@ -173,13 +179,19 @@ export const HandleVolumeBackups = ({
cronExpression: volumeBackup.cronExpression,
volumeName: volumeBackup.volumeName || "",
prefix: volumeBackup.prefix,
// keepLatestCount: volumeBackup.keepLatestCount || undefined,
keepLatestCount: volumeBackup.keepLatestCount || undefined,
turnOff: volumeBackup.turnOff,
enabled: volumeBackup.enabled || false,
serviceName: volumeBackup.serviceName || "",
destinationId: volumeBackup.destinationId,
serviceType: volumeBackup.serviceType,
});
setKeepLatestCountInput(
volumeBackup.keepLatestCount !== null &&
volumeBackup.keepLatestCount !== undefined
? String(volumeBackup.keepLatestCount)
: "",
);
}
}, [form, volumeBackup, volumeBackupId]);
@@ -190,8 +202,12 @@ export const HandleVolumeBackups = ({
const onSubmit = async (values: z.infer<typeof formSchema>) => {
if (!id && !volumeBackupId) return;
const preparedKeepLatestCount =
keepLatestCountInput === "" ? null : (values.keepLatestCount ?? null);
await mutateAsync({
...values,
keepLatestCount: preparedKeepLatestCount,
destinationId: values.destinationId,
volumeBackupId: volumeBackupId || "",
serviceType: volumeBackupType,
@@ -257,9 +273,8 @@ export const HandleVolumeBackups = ({
</DialogTrigger>
<DialogContent
className={cn(
"overflow-y-auto",
volumeBackupType === "compose" || volumeBackupType === "application"
? "max-h-[95vh] sm:max-w-2xl"
? "sm:max-w-2xl"
: " sm:max-w-lg",
)}
>
@@ -600,29 +615,38 @@ export const HandleVolumeBackups = ({
)}
/>
{/* <FormField
<FormField
control={form.control}
name="keepLatestCount"
render={({ field }) => (
<FormItem>
<FormLabel>Keep Latest Count</FormLabel>
<FormLabel>Keep Latest Backups</FormLabel>
<FormControl>
<Input
type="number"
placeholder="5"
{...field}
onChange={(e) =>
field.onChange(Number(e.target.value) || undefined)
}
type="number"
min={1}
autoComplete="off"
placeholder="Leave empty to keep all"
value={keepLatestCountInput}
onChange={(e) => {
const raw = e.target.value;
setKeepLatestCountInput(raw);
if (raw === "") {
field.onChange(undefined);
} else if (/^\d+$/.test(raw)) {
field.onChange(Number(raw));
}
}}
/>
</FormControl>
<FormDescription>
Number of backup files to keep (optional)
How many recent backups to keep. Empty means no cleanup.
</FormDescription>
<FormMessage />
</FormItem>
)}
/> */}
/>
<FormField
control={form.control}

View File

@@ -24,6 +24,7 @@ import {
FormMessage,
} from "@/components/ui/form";
import { Input } from "@/components/ui/input";
import { authClient } from "@/lib/auth-client";
import { api } from "@/utils/api";
const organizationSchema = z.object({
@@ -54,6 +55,8 @@ export function AddOrganization({ organizationId }: Props) {
const { mutateAsync, isLoading } = organizationId
? api.organization.update.useMutation()
: api.organization.create.useMutation();
const { refetch: refetchActiveOrganization } =
authClient.useActiveOrganization();
const form = useForm<OrganizationFormValues>({
resolver: zodResolver(organizationSchema),
@@ -84,6 +87,10 @@ export function AddOrganization({ organizationId }: Props) {
`Organization ${organizationId ? "updated" : "created"} successfully`,
);
utils.organization.all.invalidate();
if (organizationId) {
utils.organization.one.invalidate({ organizationId });
refetchActiveOrganization();
}
setOpen(false);
})
.catch((error) => {

View File

@@ -38,7 +38,7 @@ import { api } from "@/utils/api";
const Schema = z.object({
name: z.string().min(1, { message: "Name is required" }),
apiUrl: z.string().url({ message: "Please enter a valid URL" }),
apiKey: z.string().min(1, { message: "API Key is required" }),
apiKey: z.string(),
model: z.string().min(1, { message: "Model is required" }),
isEnabled: z.boolean(),
});
@@ -71,7 +71,7 @@ export const HandleAi = ({ aiId }: Props) => {
name: "",
apiUrl: "",
apiKey: "",
model: "gpt-3.5-turbo",
model: "",
isEnabled: true,
},
});
@@ -81,7 +81,7 @@ export const HandleAi = ({ aiId }: Props) => {
name: data?.name ?? "",
apiUrl: data?.apiUrl ?? "https://api.openai.com/v1",
apiKey: data?.apiKey ?? "",
model: data?.model ?? "gpt-3.5-turbo",
model: data?.model ?? "",
isEnabled: data?.isEnabled ?? true,
});
}, [aiId, form, data]);
@@ -89,6 +89,7 @@ export const HandleAi = ({ aiId }: Props) => {
const apiUrl = form.watch("apiUrl");
const apiKey = form.watch("apiKey");
const isOllama = apiUrl.includes(":11434") || apiUrl.includes("ollama");
const { data: models, isLoading: isLoadingServerModels } =
api.ai.getModels.useQuery(
{
@@ -96,7 +97,7 @@ export const HandleAi = ({ aiId }: Props) => {
apiKey: apiKey ?? "",
},
{
enabled: !!apiUrl && !!apiKey,
enabled: !!apiUrl && (isOllama || !!apiKey),
onError: (error) => {
setError(`Failed to fetch models: ${error.message}`);
},
@@ -191,22 +192,29 @@ export const HandleAi = ({ aiId }: Props) => {
)}
/>
<FormField
control={form.control}
name="apiKey"
render={({ field }) => (
<FormItem>
<FormLabel>API Key</FormLabel>
<FormControl>
<Input type="password" placeholder="sk-..." {...field} />
</FormControl>
<FormDescription>
Your API key for authentication
</FormDescription>
<FormMessage />
</FormItem>
)}
/>
{!isOllama && (
<FormField
control={form.control}
name="apiKey"
render={({ field }) => (
<FormItem>
<FormLabel>API Key</FormLabel>
<FormControl>
<Input
type="password"
placeholder="sk-..."
autoComplete="one-time-code"
{...field}
/>
</FormControl>
<FormDescription>
Your API key for authentication
</FormDescription>
<FormMessage />
</FormItem>
)}
/>
)}
{isLoadingServerModels && (
<span className="text-sm text-muted-foreground">
@@ -214,6 +222,12 @@ export const HandleAi = ({ aiId }: Props) => {
</span>
)}
{!isLoadingServerModels && !models?.length && (
<span className="text-sm text-muted-foreground">
No models available
</span>
)}
{!isLoadingServerModels && models && models.length > 0 && (
<FormField
control={form.control}

View File

@@ -0,0 +1 @@
ALTER TABLE "application" ADD COLUMN "previewLabels" text[];

File diff suppressed because it is too large Load Diff

View File

@@ -743,6 +743,13 @@
"when": 1754259281559,
"tag": "0105_clumsy_quicksilver",
"breakpoints": true
},
{
"idx": 106,
"version": "7",
"when": 1754912062243,
"tag": "0106_purple_maggott",
"breakpoints": true
}
]
}

View File

@@ -37,13 +37,13 @@
"test": "vitest --config __test__/vitest.config.ts"
},
"dependencies": {
"@ai-sdk/anthropic": "^1.2.12",
"@ai-sdk/azure": "^1.3.23",
"@ai-sdk/cohere": "^1.2.10",
"@ai-sdk/deepinfra": "^0.0.4",
"@ai-sdk/mistral": "^1.2.8",
"@ai-sdk/openai": "^1.3.22",
"@ai-sdk/openai-compatible": "^0.0.13",
"@ai-sdk/anthropic": "^2.0.5",
"@ai-sdk/azure": "^2.0.16",
"@ai-sdk/cohere": "^2.0.4",
"@ai-sdk/deepinfra": "^1.0.10",
"@ai-sdk/mistral": "^2.0.7",
"@ai-sdk/openai": "^2.0.16",
"@ai-sdk/openai-compatible": "^1.0.10",
"@codemirror/autocomplete": "^6.18.6",
"@codemirror/lang-json": "^6.0.1",
"@codemirror/lang-yaml": "^6.1.2",
@@ -91,7 +91,8 @@
"@xterm/addon-clipboard": "0.1.0",
"@xterm/xterm": "^5.5.0",
"adm-zip": "^0.5.16",
"ai": "^4.3.16",
"ai": "^5.0.17",
"ai-sdk-ollama": "^0.5.1",
"bcrypt": "5.1.1",
"better-auth": "v1.2.8-beta.7",
"bl": "6.0.11",
@@ -124,7 +125,6 @@
"node-schedule": "2.1.1",
"nodemailer": "6.9.14",
"octokit": "3.1.2",
"ollama-ai-provider": "^1.2.0",
"otpauth": "^9.4.0",
"pino": "9.4.0",
"pino-pretty": "11.2.2",

View File

@@ -343,7 +343,9 @@ export default async function handler(
if (
action === "opened" ||
action === "synchronize" ||
action === "reopened"
action === "reopened" ||
action === "labeled" ||
action === "unlabeled"
) {
const repository = githubBody?.repository?.name;
const deploymentHash = githubBody?.pull_request?.head?.sha;
@@ -442,6 +444,19 @@ export default async function handler(
}
for (const app of secureApps) {
// check for labels
if (app?.previewLabels && app?.previewLabels?.length > 0) {
let hasLabel = false;
const labels = githubBody?.pull_request?.labels;
for (const label of labels) {
if (app?.previewLabels?.includes(label.name)) {
hasLabel = true;
break;
}
}
if (!hasLabel) continue;
}
const previewLimit = app?.previewLimit || 0;
if (app?.previewDeployments?.length > previewLimit) {
continue;

View File

@@ -10,6 +10,7 @@ import {
FolderInput,
GlobeIcon,
Loader2,
Play,
PlusIcon,
Search,
ServerIcon,
@@ -45,6 +46,7 @@ import { BreadcrumbSidebar } from "@/components/shared/breadcrumb-sidebar";
import { DateTooltip } from "@/components/shared/date-tooltip";
import { DialogAction } from "@/components/shared/dialog-action";
import { StatusTooltip } from "@/components/shared/status-tooltip";
import { Badge } from "@/components/ui/badge";
import { Button } from "@/components/ui/button";
import {
Card,
@@ -289,6 +291,8 @@ const Project = (
const [openCombobox, setOpenCombobox] = useState(false);
const [selectedServices, setSelectedServices] = useState<string[]>([]);
const [isDropdownOpen, setIsDropdownOpen] = useState(false);
const [isBulkDeleteDialogOpen, setIsBulkDeleteDialogOpen] = useState(false);
const [deleteVolumes, setDeleteVolumes] = useState(false);
const handleSelectAll = () => {
if (selectedServices.length === filteredServices.length) {
@@ -312,6 +316,7 @@ const Project = (
stop: api.compose.stop.useMutation(),
move: api.compose.move.useMutation(),
delete: api.compose.delete.useMutation(),
deploy: api.compose.deploy.useMutation(),
};
const applicationActions = {
@@ -319,6 +324,7 @@ const Project = (
stop: api.application.stop.useMutation(),
move: api.application.move.useMutation(),
delete: api.application.delete.useMutation(),
deploy: api.application.deploy.useMutation(),
};
const postgresActions = {
@@ -326,6 +332,7 @@ const Project = (
stop: api.postgres.stop.useMutation(),
move: api.postgres.move.useMutation(),
delete: api.postgres.remove.useMutation(),
deploy: api.postgres.deploy.useMutation(),
};
const mysqlActions = {
@@ -333,6 +340,7 @@ const Project = (
stop: api.mysql.stop.useMutation(),
move: api.mysql.move.useMutation(),
delete: api.mysql.remove.useMutation(),
deploy: api.mysql.deploy.useMutation(),
};
const mariadbActions = {
@@ -340,6 +348,7 @@ const Project = (
stop: api.mariadb.stop.useMutation(),
move: api.mariadb.move.useMutation(),
delete: api.mariadb.remove.useMutation(),
deploy: api.mariadb.deploy.useMutation(),
};
const redisActions = {
@@ -347,6 +356,7 @@ const Project = (
stop: api.redis.stop.useMutation(),
move: api.redis.move.useMutation(),
delete: api.redis.remove.useMutation(),
deploy: api.redis.deploy.useMutation(),
};
const mongoActions = {
@@ -354,6 +364,7 @@ const Project = (
stop: api.mongo.stop.useMutation(),
move: api.mongo.move.useMutation(),
delete: api.mongo.remove.useMutation(),
deploy: api.mongo.deploy.useMutation(),
};
const handleBulkStart = async () => {
@@ -524,7 +535,7 @@ const Project = (
setIsBulkActionLoading(false);
};
const handleBulkDelete = async () => {
const handleBulkDelete = async (deleteVolumes = false) => {
let success = 0;
setIsBulkActionLoading(true);
for (const serviceId of selectedServices) {
@@ -541,7 +552,7 @@ const Project = (
case "compose":
await composeActions.delete.mutateAsync({
composeId: serviceId,
deleteVolumes: false,
deleteVolumes,
});
break;
case "postgres":
@@ -586,6 +597,83 @@ const Project = (
setIsBulkActionLoading(false);
};
const handleBulkDeploy = async () => {
let success = 0;
let failed = 0;
setIsBulkActionLoading(true);
for (const serviceId of selectedServices) {
try {
const service = filteredServices.find((s) => s.id === serviceId);
if (!service) continue;
switch (service.type) {
case "application":
await applicationActions.deploy.mutateAsync({
applicationId: serviceId,
});
break;
case "compose":
await composeActions.deploy.mutateAsync({
composeId: serviceId,
});
break;
case "postgres":
await postgresActions.deploy.mutateAsync({
postgresId: serviceId,
});
break;
case "mysql":
await mysqlActions.deploy.mutateAsync({
mysqlId: serviceId,
});
break;
case "mariadb":
await mariadbActions.deploy.mutateAsync({
mariadbId: serviceId,
});
break;
case "redis":
await redisActions.deploy.mutateAsync({
redisId: serviceId,
});
break;
case "mongo":
await mongoActions.deploy.mutateAsync({
mongoId: serviceId,
});
break;
}
success++;
} catch (error) {
failed++;
toast.error(
`Error deploying service ${serviceId}: ${error instanceof Error ? error.message : "Unknown error"}`,
);
}
}
if (success > 0) {
toast.success(
`${success} service${success !== 1 ? "s" : ""} deployed successfully`,
);
}
if (failed > 0) {
toast.error(
`${failed} service${failed !== 1 ? "s" : ""} failed to deploy`,
);
}
setSelectedServices([]);
setIsDropdownOpen(false);
setIsBulkActionLoading(false);
};
const filteredServices = useMemo(() => {
if (!applications) return [];
const filtered = applications.filter(
@@ -729,6 +817,24 @@ const Project = (
Start
</Button>
</DialogAction>
<DialogAction
title="Deploy Services"
description={`Are you sure you want to deploy ${selectedServices.length} service${selectedServices.length !== 1 ? "s" : ""}? This will redeploy/restart the selected services.`}
onClick={handleBulkDeploy}
type="default"
disabled={
selectedServices.length === 0 ||
isBulkActionLoading
}
>
<Button
variant="ghost"
className="w-full justify-start"
>
<Play className="mr-2 h-4 w-4" />
Deploy
</Button>
</DialogAction>
<DialogAction
title="Stop Services"
description={`Are you sure you want to stop ${selectedServices.length} services?`}
@@ -776,7 +882,7 @@ const Project = (
disabled={
selectedServicesWithRunningStatus.length > 0
}
onClick={handleBulkDelete}
onClick={() => setIsBulkDeleteDialogOpen(true)}
>
<Button
variant="ghost"
@@ -872,6 +978,113 @@ const Project = (
</DialogFooter>
</DialogContent>
</Dialog>
{/* Bulk Delete Dialog */}
<Dialog
open={isBulkDeleteDialogOpen}
onOpenChange={setIsBulkDeleteDialogOpen}
>
<DialogContent>
<DialogHeader>
<DialogTitle>Delete Services</DialogTitle>
<DialogDescription>
Are you sure you want to delete{" "}
{selectedServices.length} service
{selectedServices.length !== 1 ? "s" : ""}?
This action cannot be undone.
</DialogDescription>
</DialogHeader>
<div className="space-y-4">
{/* Show services to be deleted */}
<div className="max-h-40 overflow-y-auto space-y-2">
{selectedServices.map((serviceId) => {
const service = filteredServices.find(
(s) => s.id === serviceId,
);
return service ? (
<div
key={serviceId}
className="flex items-center space-x-2 text-sm"
>
<span className="px-2 py-1 text-xs bg-secondary rounded">
{service.type}
</span>
<span>{service.name}</span>
</div>
) : null;
})}
</div>
{/* Volume deletion option for compose services */}
{(() => {
const servicesWithVolumeSupport =
selectedServices.filter((serviceId) => {
const service = filteredServices.find(
(s) => s.id === serviceId,
);
// Currently only compose services support volume deletion
return service?.type === "compose";
});
if (servicesWithVolumeSupport.length === 0)
return null;
return (
<div className="space-y-2">
<div className="flex items-center space-x-2">
<Checkbox
id="deleteVolumes"
checked={deleteVolumes}
onCheckedChange={(checked) =>
setDeleteVolumes(checked === true)
}
/>
<label
htmlFor="deleteVolumes"
className="text-sm font-medium"
>
Delete volumes associated with
services
</label>
</div>
<p className="text-xs text-muted-foreground">
Volume deletion is available for:{" "}
{servicesWithVolumeSupport.length}{" "}
compose service
{servicesWithVolumeSupport.length !== 1
? "s"
: ""}
</p>
</div>
);
})()}
</div>
<DialogFooter>
<Button
variant="outline"
onClick={() => {
setIsBulkDeleteDialogOpen(false);
setDeleteVolumes(false); // Reset checkbox
}}
>
Cancel
</Button>
<Button
variant="destructive"
onClick={() => {
handleBulkDelete(deleteVolumes);
setIsBulkDeleteDialogOpen(false);
setDeleteVolumes(false); // Reset checkbox
}}
disabled={isBulkActionLoading}
>
Delete Services
</Button>
</DialogFooter>
</DialogContent>
</Dialog>
</DropdownMenuContent>
</DropdownMenu>
</div>

View File

@@ -329,6 +329,7 @@ export default function Home({ IS_CLOUD }: Props) {
maxLength={6}
pattern={REGEXP_ONLY_DIGITS}
autoComplete="off"
autoFocus
>
<InputOTPGroup>
<InputOTPSlot index={0} className="border-border" />

View File

@@ -20,6 +20,7 @@ import {
} from "@dokploy/server/services/user";
import {
getProviderHeaders,
getProviderName,
type Model,
} from "@dokploy/server/utils/ai/select-ai-provider";
import { TRPCError } from "@trpc/server";
@@ -47,11 +48,24 @@ export const aiRouter = createTRPCRouter({
}),
getModels: protectedProcedure
.input(z.object({ apiUrl: z.string().min(1), apiKey: z.string().min(1) }))
.input(z.object({ apiUrl: z.string().min(1), apiKey: z.string() }))
.query(async ({ input }) => {
try {
const providerName = getProviderName(input.apiUrl);
const headers = getProviderHeaders(input.apiUrl, input.apiKey);
const response = await fetch(`${input.apiUrl}/models`, { headers });
let response = null;
switch (providerName) {
case "ollama":
response = await fetch(`${input.apiUrl}/api/tags`, { headers });
break;
default:
if (!input.apiKey)
throw new TRPCError({
code: "BAD_REQUEST",
message: "API key must contain at least 1 character(s)",
});
response = await fetch(`${input.apiUrl}/models`, { headers });
}
if (!response.ok) {
const errorText = await response.text();

View File

@@ -21,7 +21,7 @@ import {
await initializeNetwork();
createDefaultTraefikConfig();
createDefaultServerTraefikConfig();
await execAsync("docker pull traefik:v3.1.2");
await execAsync("docker pull traefik:v3.5.0");
await initializeStandaloneTraefik();
await initializeRedis();
await initializePostgres();

View File

@@ -28,13 +28,13 @@
"typecheck": "tsc --noEmit"
},
"dependencies": {
"@ai-sdk/anthropic": "^1.2.12",
"@ai-sdk/azure": "^1.3.23",
"@ai-sdk/cohere": "^1.2.10",
"@ai-sdk/deepinfra": "^0.0.4",
"@ai-sdk/mistral": "^1.2.8",
"@ai-sdk/openai": "^1.3.22",
"@ai-sdk/openai-compatible": "^0.0.13",
"@ai-sdk/anthropic": "^2.0.5",
"@ai-sdk/azure": "^2.0.16",
"@ai-sdk/cohere": "^2.0.4",
"@ai-sdk/deepinfra": "^1.0.10",
"@ai-sdk/mistral": "^2.0.7",
"@ai-sdk/openai": "^2.0.16",
"@ai-sdk/openai-compatible": "^1.0.10",
"@better-auth/utils": "0.2.4",
"@faker-js/faker": "^8.4.1",
"@octokit/auth-app": "^6.1.3",
@@ -44,7 +44,8 @@
"@react-email/components": "^0.0.21",
"@trpc/server": "^10.45.2",
"adm-zip": "^0.5.16",
"ai": "^4.3.16",
"ai": "^5.0.17",
"ai-sdk-ollama": "^0.5.1",
"bcrypt": "5.1.1",
"better-auth": "v1.2.8-beta.7",
"bl": "6.0.11",
@@ -65,7 +66,6 @@
"node-schedule": "2.1.1",
"nodemailer": "6.9.14",
"octokit": "3.1.2",
"ollama-ai-provider": "^1.2.0",
"otpauth": "^9.4.0",
"pino": "9.4.0",
"pino-pretty": "11.2.2",

View File

@@ -32,7 +32,7 @@ export const aiRelations = relations(ai, ({ one }) => ({
const createSchema = createInsertSchema(ai, {
name: z.string().min(1, { message: "Name is required" }),
apiUrl: z.string().url({ message: "Please enter a valid URL" }),
apiKey: z.string().min(1, { message: "API Key is required" }),
apiKey: z.string(),
model: z.string().min(1, { message: "Model is required" }),
isEnabled: z.boolean().optional(),
});

View File

@@ -79,6 +79,7 @@ export const applications = pgTable("application", {
previewEnv: text("previewEnv"),
watchPaths: text("watchPaths").array(),
previewBuildArgs: text("previewBuildArgs"),
previewLabels: text("previewLabels").array(),
previewWildcard: text("previewWildcard"),
previewPort: integer("previewPort").default(3000),
previewHttps: boolean("previewHttps").notNull().default(false),
@@ -308,6 +309,7 @@ const createSchema = createInsertSchema(applications, {
previewCertificateType: z.enum(["letsencrypt", "none", "custom"]).optional(),
previewRequireCollaboratorPermissions: z.boolean().optional(),
watchPaths: z.array(z.string()).optional(),
previewLabels: z.array(z.string()).optional(),
cleanCache: z.boolean().optional(),
});

View File

@@ -70,7 +70,7 @@ export const findApplicationByPreview = async (applicationId: string) => {
if (!application) {
throw new TRPCError({
code: "NOT_FOUND",
message: "Applicationnot found",
message: "Application not found",
});
}
return application;
@@ -78,35 +78,41 @@ export const findApplicationByPreview = async (applicationId: string) => {
export const removePreviewDeployment = async (previewDeploymentId: string) => {
try {
const application = await findApplicationByPreview(previewDeploymentId);
const previewDeployment =
await findPreviewDeploymentById(previewDeploymentId);
const deployment = await db
.delete(previewDeployments)
.where(eq(previewDeployments.previewDeploymentId, previewDeploymentId))
.returning();
const application = await findApplicationById(
previewDeployment.applicationId,
);
application.appName = previewDeployment.appName;
const cleanupOperations = [
async () =>
await removeService(application?.appName, application?.serverId),
async () =>
await removeDeploymentsByPreviewDeploymentId(
previewDeployment,
application.serverId,
application?.serverId,
),
async () =>
await removeDirectoryCode(application.appName, application.serverId),
await removeDirectoryCode(application?.appName, application?.serverId),
async () =>
await removeTraefikConfig(application.appName, application.serverId),
await removeTraefikConfig(application?.appName, application?.serverId),
async () =>
await removeService(application?.appName, application.serverId),
await db
.delete(previewDeployments)
.where(
eq(previewDeployments.previewDeploymentId, previewDeploymentId),
)
.returning(),
];
for (const operation of cleanupOperations) {
try {
await operation();
} catch {}
} catch (error) {
console.error(error);
}
}
return deployment[0];
return previewDeployment;
} catch (error) {
const message =
error instanceof Error

View File

@@ -13,7 +13,7 @@ export const TRAEFIK_PORT =
Number.parseInt(process.env.TRAEFIK_PORT!, 10) || 80;
export const TRAEFIK_HTTP3_PORT =
Number.parseInt(process.env.TRAEFIK_HTTP3_PORT!, 10) || 443;
export const TRAEFIK_VERSION = process.env.TRAEFIK_VERSION || "3.1.2";
export const TRAEFIK_VERSION = process.env.TRAEFIK_VERSION || "3.5.0";
export interface TraefikOptions {
env?: string[];

View File

@@ -5,17 +5,16 @@ import { createDeepInfra } from "@ai-sdk/deepinfra";
import { createMistral } from "@ai-sdk/mistral";
import { createOpenAI } from "@ai-sdk/openai";
import { createOpenAICompatible } from "@ai-sdk/openai-compatible";
import { createOllama } from "ollama-ai-provider";
import { createOllama } from "ai-sdk-ollama";
function getProviderName(apiUrl: string) {
export function getProviderName(apiUrl: string) {
if (apiUrl.includes("api.openai.com")) return "openai";
if (apiUrl.includes("azure.com")) return "azure";
if (apiUrl.includes("api.anthropic.com")) return "anthropic";
if (apiUrl.includes("api.cohere.ai")) return "cohere";
if (apiUrl.includes("api.perplexity.ai")) return "perplexity";
if (apiUrl.includes("api.mistral.ai")) return "mistral";
if (apiUrl.includes("localhost:11434") || apiUrl.includes("ollama"))
return "ollama";
if (apiUrl.includes(":11434") || apiUrl.includes("ollama")) return "ollama";
if (apiUrl.includes("api.deepinfra.com")) return "deepinfra";
return "custom";
}

View File

@@ -254,6 +254,9 @@ export const addDomainToCompose = async (
if (!labels.includes("traefik.docker.network=dokploy-network")) {
labels.unshift("traefik.docker.network=dokploy-network");
}
if (!labels.includes("traefik.swarm.network=dokploy-network")) {
labels.unshift("traefik.swarm.network=dokploy-network");
}
}
}

View File

@@ -273,6 +273,14 @@ export const prepareEnvironmentVariables = (
throw new Error(`Invalid project environment variable: project.${ref}`);
});
}
resolvedValue = resolvedValue.replace(/\$\{\{(.*?)\}\}/g, (_, ref) => {
if (serviceVars[ref] !== undefined) {
return serviceVars[ref];
}
throw new Error(`Invalid service environment variable: ${ref}`);
});
return `${key}=${resolvedValue}`;
});

View File

@@ -2,11 +2,14 @@ import { findVolumeBackupById } from "@dokploy/server/services/volume-backups";
import { scheduledJobs, scheduleJob } from "node-schedule";
import {
createDeploymentVolumeBackup,
updateDeploymentStatus,
} from "@dokploy/server/services/deployment";
import {
execAsync,
execAsyncRemote,
updateDeploymentStatus,
} from "../..";
} from "@dokploy/server/utils/process/execAsync";
import { backupVolume } from "./backup";
import { getS3Credentials, normalizeS3Path } from "../backups/utils";
export const scheduleVolumeBackup = async (volumeBackupId: string) => {
const volumeBackup = await findVolumeBackupById(volumeBackupId);
@@ -20,6 +23,33 @@ export const removeVolumeBackupJob = async (volumeBackupId: string) => {
currentJob?.cancel();
};
const cleanupOldVolumeBackups = async (
volumeBackup: Awaited<ReturnType<typeof findVolumeBackupById>>,
serverId?: string | null,
) => {
const { keepLatestCount, destination, prefix, volumeName } = volumeBackup;
if (!keepLatestCount) return;
try {
const rcloneFlags = getS3Credentials(destination);
const normalizedPrefix = normalizeS3Path(prefix);
const backupFilesPath = `:s3:${destination.bucket}/${normalizedPrefix}`;
const listCommand = `rclone lsf ${rcloneFlags.join(" ")} --include \"${volumeName}-*.tar\" :s3:${destination.bucket}/${normalizedPrefix}`;
const sortAndPick = `sort -r | tail -n +$((${keepLatestCount}+1)) | xargs -I{}`;
const deleteCommand = `rclone delete ${rcloneFlags.join(" ")} ${backupFilesPath}{}`;
const fullCommand = `${listCommand} | ${sortAndPick} ${deleteCommand}`;
if (serverId) {
await execAsyncRemote(serverId, fullCommand);
} else {
await execAsync(fullCommand);
}
} catch (error) {
console.error("Volume backup retention error", error);
}
};
export const runVolumeBackup = async (volumeBackupId: string) => {
const volumeBackup = await findVolumeBackupById(volumeBackupId);
const serverId =
@@ -40,6 +70,10 @@ export const runVolumeBackup = async (volumeBackupId: string) => {
await execAsync(commandWithLog);
}
if (volumeBackup.keepLatestCount && volumeBackup.keepLatestCount > 0) {
await cleanupOldVolumeBackups(volumeBackup, serverId);
}
await updateDeploymentStatus(deployment.deploymentId, "done");
} catch (error) {
await updateDeploymentStatus(deployment.deploymentId, "error");

364
pnpm-lock.yaml generated
View File

@@ -101,26 +101,26 @@ importers:
apps/dokploy:
dependencies:
'@ai-sdk/anthropic':
specifier: ^1.2.12
version: 1.2.12(zod@3.25.32)
specifier: ^2.0.5
version: 2.0.5(zod@3.25.32)
'@ai-sdk/azure':
specifier: ^1.3.23
version: 1.3.23(zod@3.25.32)
specifier: ^2.0.16
version: 2.0.16(zod@3.25.32)
'@ai-sdk/cohere':
specifier: ^1.2.10
version: 1.2.10(zod@3.25.32)
specifier: ^2.0.4
version: 2.0.4(zod@3.25.32)
'@ai-sdk/deepinfra':
specifier: ^0.0.4
version: 0.0.4(zod@3.25.32)
specifier: ^1.0.10
version: 1.0.10(zod@3.25.32)
'@ai-sdk/mistral':
specifier: ^1.2.8
version: 1.2.8(zod@3.25.32)
specifier: ^2.0.7
version: 2.0.7(zod@3.25.32)
'@ai-sdk/openai':
specifier: ^1.3.22
version: 1.3.22(zod@3.25.32)
specifier: ^2.0.16
version: 2.0.16(zod@3.25.32)
'@ai-sdk/openai-compatible':
specifier: ^0.0.13
version: 0.0.13(zod@3.25.32)
specifier: ^1.0.10
version: 1.0.10(zod@3.25.32)
'@codemirror/autocomplete':
specifier: ^6.18.6
version: 6.18.6
@@ -263,8 +263,11 @@ importers:
specifier: ^0.5.16
version: 0.5.16
ai:
specifier: ^4.3.16
version: 4.3.16(react@18.2.0)(zod@3.25.32)
specifier: ^5.0.17
version: 5.0.17(zod@3.25.32)
ai-sdk-ollama:
specifier: ^0.5.1
version: 0.5.1(zod@3.25.32)
bcrypt:
specifier: 5.1.1
version: 5.1.1
@@ -361,9 +364,6 @@ importers:
octokit:
specifier: 3.1.2
version: 3.1.2
ollama-ai-provider:
specifier: ^1.2.0
version: 1.2.0(zod@3.25.32)
otpauth:
specifier: ^9.4.0
version: 9.4.0
@@ -595,26 +595,26 @@ importers:
packages/server:
dependencies:
'@ai-sdk/anthropic':
specifier: ^1.2.12
version: 1.2.12(zod@3.25.32)
specifier: ^2.0.5
version: 2.0.5(zod@3.25.32)
'@ai-sdk/azure':
specifier: ^1.3.23
version: 1.3.23(zod@3.25.32)
specifier: ^2.0.16
version: 2.0.16(zod@3.25.32)
'@ai-sdk/cohere':
specifier: ^1.2.10
version: 1.2.10(zod@3.25.32)
specifier: ^2.0.4
version: 2.0.4(zod@3.25.32)
'@ai-sdk/deepinfra':
specifier: ^0.0.4
version: 0.0.4(zod@3.25.32)
specifier: ^1.0.10
version: 1.0.10(zod@3.25.32)
'@ai-sdk/mistral':
specifier: ^1.2.8
version: 1.2.8(zod@3.25.32)
specifier: ^2.0.7
version: 2.0.7(zod@3.25.32)
'@ai-sdk/openai':
specifier: ^1.3.22
version: 1.3.22(zod@3.25.32)
specifier: ^2.0.16
version: 2.0.16(zod@3.25.32)
'@ai-sdk/openai-compatible':
specifier: ^0.0.13
version: 0.0.13(zod@3.25.32)
specifier: ^1.0.10
version: 1.0.10(zod@3.25.32)
'@better-auth/utils':
specifier: 0.2.4
version: 0.2.4
@@ -643,8 +643,11 @@ importers:
specifier: ^0.5.16
version: 0.5.16
ai:
specifier: ^4.3.16
version: 4.3.16(react@18.2.0)(zod@3.25.32)
specifier: ^5.0.17
version: 5.0.17(zod@3.25.32)
ai-sdk-ollama:
specifier: ^0.5.1
version: 0.5.1(zod@3.25.32)
bcrypt:
specifier: 5.1.1
version: 5.1.1
@@ -705,9 +708,6 @@ importers:
octokit:
specifier: 3.1.2
version: 3.1.2
ollama-ai-provider:
specifier: ^1.2.0
version: 1.2.0(zod@3.25.32)
otpauth:
specifier: ^9.4.0
version: 9.4.0
@@ -823,87 +823,64 @@ importers:
packages:
'@ai-sdk/anthropic@1.2.12':
resolution: {integrity: sha512-YSzjlko7JvuiyQFmI9RN1tNZdEiZxc+6xld/0tq/VkJaHpEzGAb1yiNxxvmYVcjvfu/PcvCxAAYXmTYQQ63IHQ==}
'@ai-sdk/anthropic@2.0.5':
resolution: {integrity: sha512-f0+mD3c5D+ImCWqxFxkT3buGeBg9vFOd2aTaLd1jjIJmWO8O4INLxBC2ETif7z0BfegTIw528B6acBRIeg3jIw==}
engines: {node: '>=18'}
peerDependencies:
zod: ^3.0.0
zod: ^3.25.76 || ^4
'@ai-sdk/azure@1.3.23':
resolution: {integrity: sha512-vpsaPtU24RBVk/IMM5UylR/N4RtAuL2NZLWc7LJ3tvMTHu6pI46a7w+1qIwR3F6yO9ehWR8qvfLaBefJNFxaVw==}
'@ai-sdk/azure@2.0.16':
resolution: {integrity: sha512-Q8Fq7aJP9tJOCNicfEUDxU763NkX646zCePayy2Nse+5Gz6ElQEJ9MApIdC4LAyR/IsEuY8G5iY477GAF+iBjg==}
engines: {node: '>=18'}
peerDependencies:
zod: ^3.0.0
zod: ^3.25.76 || ^4
'@ai-sdk/cohere@1.2.10':
resolution: {integrity: sha512-OaUwd5xj4bxSO8hdCbX1a5uUlTouU8FcodSuRON6xDSsmjZIvQL4O2G1XzcidxiQVL8JQuA+M0tHZOwGxSL96A==}
'@ai-sdk/cohere@2.0.4':
resolution: {integrity: sha512-GkQsTmhDNVDv8OiwWEGBhkjWYXFzBSbRtmyaM5y4yr9h6rqNwRiwueJG89/aX4mqpGf431mnJCPYxD+eSV6vgQ==}
engines: {node: '>=18'}
peerDependencies:
zod: ^3.0.0
zod: ^3.25.76 || ^4
'@ai-sdk/deepinfra@0.0.4':
resolution: {integrity: sha512-0YZpe7bFWpSZpk2swBhYsKyd2DjxyMa0bJTajJjwec4UGUpuiDyhjXkBgEHY85JjlTubEPby8Ix+FgU0E3ofnw==}
'@ai-sdk/deepinfra@1.0.10':
resolution: {integrity: sha512-bAvg29LkPI04o9ehRyqwjkKAUlX6W8AXlBTzX/k/gMez9qBbiW6uDpRRXTNYbaEuMONrEHgOQLh2OQ5WYJLDPg==}
engines: {node: '>=18'}
peerDependencies:
zod: ^3.0.0
zod: ^3.25.76 || ^4
'@ai-sdk/mistral@1.2.8':
resolution: {integrity: sha512-lv857D9UJqCVxiq2Fcu7mSPTypEHBUqLl1K+lCaP6X/7QAkcaxI36QDONG+tOhGHJOXTsS114u8lrUTaEiGXbg==}
'@ai-sdk/gateway@1.0.8':
resolution: {integrity: sha512-yiHYz0bAHEvhL+fSUBI2dNmyj0LOI8zw5qrYpa4gp1ojPgZq/7T1WXoIWRmVdjQwvT4PzSmRKLtbMPfz+umgfw==}
engines: {node: '>=18'}
peerDependencies:
zod: ^3.0.0
zod: ^3.25.76 || ^4
'@ai-sdk/openai-compatible@0.0.13':
resolution: {integrity: sha512-fuauXYKac6PBuf8m52tWcWQW0UCScEkwTaOyr00TcPeK3dd8nPP+ZJzSYE5QhFg7rwi9EH3ahIFqSX1biXhdkQ==}
'@ai-sdk/mistral@2.0.7':
resolution: {integrity: sha512-wWBacWHZHx+WxwjSqb5iIdtK76tmBiEb35ZBLmjODFEdh/dMIaj+g/qMVFT9PY7hHxZ1sT9C58KS671l8nAISw==}
engines: {node: '>=18'}
peerDependencies:
zod: ^3.0.0
zod: ^3.25.76 || ^4
'@ai-sdk/openai@1.3.22':
resolution: {integrity: sha512-QwA+2EkG0QyjVR+7h6FE7iOu2ivNqAVMm9UJZkVxxTk5OIq5fFJDTEI/zICEMuHImTTXR2JjsL6EirJ28Jc4cw==}
'@ai-sdk/openai-compatible@1.0.10':
resolution: {integrity: sha512-NInkII/DOvrMvO/mS0BxGUGi3r+wuXxbdzAh9k2gFGT+xVoP6OePikhogQu8qZuti8loUZJGYq3GJ/DCftOzhQ==}
engines: {node: '>=18'}
peerDependencies:
zod: ^3.0.0
zod: ^3.25.76 || ^4
'@ai-sdk/provider-utils@2.0.5':
resolution: {integrity: sha512-2M7vLhYN0ThGjNlzow7oO/lsL+DyMxvGMIYmVQvEYaCWhDzxH5dOp78VNjJIVwHzVLMbBDigX3rJuzAs853idw==}
'@ai-sdk/openai@2.0.16':
resolution: {integrity: sha512-Boe715q4SkSJedFfAtbP0yuo8DmF9iYElAaDH2g4YgqJqqkskIJJx4hlCYGMMk1eMesRrB2NqZvtOeyTZ/u4fA==}
engines: {node: '>=18'}
peerDependencies:
zod: ^3.0.0
peerDependenciesMeta:
zod:
optional: true
zod: ^3.25.76 || ^4
'@ai-sdk/provider-utils@2.2.8':
resolution: {integrity: sha512-fqhG+4sCVv8x7nFzYnFo19ryhAa3w096Kmc3hWxMQfW/TubPOmt3A6tYZhl4mUfQWWQMsuSkLrtjlWuXBVSGQA==}
'@ai-sdk/provider-utils@3.0.4':
resolution: {integrity: sha512-/3Z6lfUp8r+ewFd9yzHkCmPlMOJUXup2Sx3aoUyrdXLhOmAfHRl6Z4lDbIdV0uvw/QYoBcVLJnvXN7ncYeS3uQ==}
engines: {node: '>=18'}
peerDependencies:
zod: ^3.23.8
zod: ^3.25.76 || ^4
'@ai-sdk/provider@1.0.3':
resolution: {integrity: sha512-WiuJEpHTrltOIzv3x2wx4gwksAHW0h6nK3SoDzjqCOJLu/2OJ1yASESTIX+f07ChFykHElVoP80Ol/fe9dw6tQ==}
'@ai-sdk/provider@2.0.0':
resolution: {integrity: sha512-6o7Y2SeO9vFKB8lArHXehNuusnpddKPk7xqL7T2/b+OvXMRIXUO1rR4wcv1hAFUAT9avGZshty3Wlua/XA7TvA==}
engines: {node: '>=18'}
'@ai-sdk/provider@1.1.3':
resolution: {integrity: sha512-qZMxYJ0qqX/RfnuIaab+zp8UAeJn/ygXXAffR5I4N0n1IrvA6qBsjc8hXLmBiMV2zoXlifkacF7sEFnYnjBcqg==}
engines: {node: '>=18'}
'@ai-sdk/react@1.2.12':
resolution: {integrity: sha512-jK1IZZ22evPZoQW3vlkZ7wvjYGYF+tRBKXtrcolduIkQ/m/sOAVcVeVDUDvh1T91xCnWCdUGCPZg2avZ90mv3g==}
engines: {node: '>=18'}
peerDependencies:
react: ^18 || ^19 || ^19.0.0-rc
zod: ^3.23.8
peerDependenciesMeta:
zod:
optional: true
'@ai-sdk/ui-utils@1.2.11':
resolution: {integrity: sha512-3zcwCc8ezzFlwp3ZD15wAPjf2Au4s3vAbKsXQVyhxODHcmu0iyPO2Eua6D/vicq/AUm/BAo60r97O6HU+EI0+w==}
engines: {node: '>=18'}
peerDependencies:
zod: ^3.23.8
'@alloc/quick-lru@5.2.0':
resolution: {integrity: sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==}
engines: {node: '>=10'}
@@ -3716,6 +3693,9 @@ packages:
resolution: {integrity: sha512-TV7t8GKYaJWsn00tFDqBw8+Uqmr8A0fRU1tvTQhyZzGv0sJCGRQL3JGMI3ucuKo3XIZdUP+Lx7/gh2t3lewy7g==}
engines: {node: '>=14.16'}
'@standard-schema/spec@1.0.0':
resolution: {integrity: sha512-m2bOd0f2RT9k8QJx1JN85cZYyH1RqFBdlwtkSlf4tBDYLCiiZnv1fIIwacK6cqwXavOydf0NPToMQgpKq+dVlA==}
'@stepperize/react@4.0.1':
resolution: {integrity: sha512-LAOcfi3d2mM/Jn740Xy35qsuTwmoLIuitvWZTZRURYeGsc7a6sIKAkk3+L1joZGkLFvf5q4I6V7LxWWfB5hDvg==}
peerDependencies:
@@ -3958,9 +3938,6 @@ packages:
'@types/debug@4.1.12':
resolution: {integrity: sha512-vIChWdVG3LG1SMxEvI/AK+FWJthlrqlTu7fbrlywTkkaONwk/UAGaULXRlf8vkzFBLVm0zkMdCquhL5aOjhXPQ==}
'@types/diff-match-patch@1.0.36':
resolution: {integrity: sha512-xFdR6tkm0MWvBfO8xXCSsinYxHcqkQUlcHeSpMC2ukzOb6lwQAfDmW+Qt0AvlGd8HpsS28qKsB+oPeJn9I39jg==}
'@types/docker-modem@3.0.6':
resolution: {integrity: sha512-yKpAGEuKRSS8wwx0joknWxsmLha78wNMe9R2S3UNsVOkZded8UqOrV8KoeDXoXsjndxwyF3eIhyClGbO1SEhEg==}
@@ -4201,15 +4178,15 @@ packages:
resolution: {integrity: sha512-0poP0T7el6Vq3rstR8Mn4V/IQrpBLO6POkUSrN7RhyY+GF/InCFShQzsQ39T25gkHhLgSLByyAz+Kjb+c2L98w==}
engines: {node: '>=12'}
ai@4.3.16:
resolution: {integrity: sha512-KUDwlThJ5tr2Vw0A1ZkbDKNME3wzWhuVfAOwIvFUzl1TPVDFAXDFTXio3p+jaKneB+dKNCvFFlolYmmgHttG1g==}
ai-sdk-ollama@0.5.1:
resolution: {integrity: sha512-VPE2yagxtowepiPROaP/7YUpiZxqZO4SDHHM2Tdw0wyatPCggct142eQI34UO2/PPJ1iXKpraWI5+n0/pcz69Q==}
engines: {node: '>=22'}
ai@5.0.17:
resolution: {integrity: sha512-DLZikqZZJdwSkRhFikw6Mt7pUmPZ7Ue38TjdOcw2U6iZtBbuiyWGIhHyJXlUpLcZrtBE5yqPTozyZri1lRjduw==}
engines: {node: '>=18'}
peerDependencies:
react: ^18 || ^19 || ^19.0.0-rc
zod: ^3.23.8
peerDependenciesMeta:
react:
optional: true
zod: ^3.25.76 || ^4
ajv@8.17.1:
resolution: {integrity: sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==}
@@ -4846,9 +4823,6 @@ packages:
didyoumean@1.2.2:
resolution: {integrity: sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==}
diff-match-patch@1.0.5:
resolution: {integrity: sha512-IayShXAgj/QMXgB0IWmKx+rOPuGMhqm5w6jvFxmVenXKIzRqTAAsbBPT3kWQeGANj3jGgvcvv4yK6SxqYmikgw==}
diff-sequences@29.6.3:
resolution: {integrity: sha512-EjePK1srD3P08o2j4f0ExnylqRs5B9tJjcp9t1krH2qRi8CCdsYfwe9JgSLurFBWwq4uOlipzfk5fHNvwFKr8Q==}
engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
@@ -5135,9 +5109,9 @@ packages:
resolution: {integrity: sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==}
engines: {node: '>=0.8.x'}
eventsource-parser@3.0.2:
resolution: {integrity: sha512-6RxOBZ/cYgd8usLwsEl+EC09Au/9BcmCKYF2/xbml6DNczf7nv0MQb+7BA2F+li6//I+28VNlQR37XfQtcAJuA==}
engines: {node: '>=18.0.0'}
eventsource-parser@3.0.5:
resolution: {integrity: sha512-bSRG85ZrMdmWtm7qkF9He9TNRzc/Bm99gEJMaQoHJ9E6Kv9QBbsldh2oMj7iXmYNEAVvNgvv5vPorG6W+XtBhQ==}
engines: {node: '>=20.0.0'}
execa@8.0.1:
resolution: {integrity: sha512-VyhnebXciFV2DESc+p6B+y0LjSm0krU4OgJN44qFAhBY0TJ+1V61tYD2+wHusZ6F9n5K+vl8k0sTy7PEfV4qpg==}
@@ -5730,11 +5704,6 @@ packages:
json-stringify-safe@5.0.1:
resolution: {integrity: sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA==}
jsondiffpatch@0.6.0:
resolution: {integrity: sha512-3QItJOXp2AP1uv7waBkao5nCvhEv+QmJAd38Ybq7wNI74Q+BBmnLn4EDKz6yI9xGAIQoUF87qHt+kc1IVxB4zQ==}
engines: {node: ^18.0.0 || >=20.0.0}
hasBin: true
jsonparse@1.3.1:
resolution: {integrity: sha512-POQXvpdL69+CluYsillJ7SUhKvytYjW9vG/GKpnf+xP8UWgYEM/RaMzHHofbALDiKbbP1W8UEYmgGl39WkPZsg==}
engines: {'0': node >= 0.2.0}
@@ -6395,14 +6364,8 @@ packages:
resolution: {integrity: sha512-MG5qmrTL5y8KYwFgE1A4JWmgfQBaIETE/lOlfwNYx1QOtCQHGVxkRJmdUJltFc1HVn73d61TlMhMyNTOtMl+ng==}
engines: {node: '>= 18'}
ollama-ai-provider@1.2.0:
resolution: {integrity: sha512-jTNFruwe3O/ruJeppI/quoOUxG7NA6blG3ZyQj3lei4+NnJo7bi3eIRWqlVpRlu/mbzbFXeJSBuYQWF6pzGKww==}
engines: {node: '>=18'}
peerDependencies:
zod: ^3.0.0
peerDependenciesMeta:
zod:
optional: true
ollama@0.5.17:
resolution: {integrity: sha512-q5LmPtk6GLFouS+3aURIVl+qcAOPC4+Msmx7uBb3pd+fxI55WnGjmLZ0yijI/CYy79x0QPGx3BwC3u5zv9fBvQ==}
on-exit-leak-free@2.1.2:
resolution: {integrity: sha512-0eJJY6hXLGf1udHwfNftBqH+g73EU4B504nZeKpz1sYRKafAghwxEJunB2O7rDZkL4PGfsMVnTXZ2EjibbqcsA==}
@@ -6485,9 +6448,6 @@ packages:
resolution: {integrity: sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==}
engines: {node: '>= 0.8'}
partial-json@0.1.7:
resolution: {integrity: sha512-Njv/59hHaokb/hRUjce3Hdv12wd60MtM9Z5Olmn+nehe0QDAsRtRbJPvJ0Z91TusF0SuZRIvnM+S4l6EIP8leA==}
path-exists@4.0.0:
resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==}
engines: {node: '>=8'}
@@ -7349,11 +7309,6 @@ packages:
react: '>=16.8.0 <19'
react-dom: '>=16.8.0 <19'
swr@2.3.3:
resolution: {integrity: sha512-dshNvs3ExOqtZ6kJBaAsabhPdHyeY4P2cKwRCniDVifBMoG/SVI7tfLWqPXriVspf2Rg4tPzXJTnwaihIeFw2A==}
peerDependencies:
react: ^16.11.0 || ^17.0.0 || ^18.0.0 || ^19.0.0
symbol-observable@1.2.0:
resolution: {integrity: sha512-e900nM8RRtGhlV36KGEU9k65K3mPb1WV70OdjfxlG2EAuM1noi/E/BaW/uMhL7bPEssK8QV57vN3esixjUvcXQ==}
engines: {node: '>=0.10.0'}
@@ -7414,10 +7369,6 @@ packages:
thread-stream@3.1.0:
resolution: {integrity: sha512-OqyPZ9u96VohAyMfJykzmivOrY2wfMSf3C5TtFJVgN+Hm6aj+voFhlK+kZEIv2FBh1X6Xp3DlnCOfEQ3B2J86A==}
throttleit@2.1.0:
resolution: {integrity: sha512-nt6AMGKW1p/70DF/hGBdJB57B8Tspmbp5gfJ8ilhLnt7kkr2ye7hzD6NVG8GGErk2HWF34igrL2CXmNIkzKqKw==}
engines: {node: '>=18'}
through@2.3.8:
resolution: {integrity: sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==}
@@ -7735,6 +7686,9 @@ packages:
webidl-conversions@3.0.1:
resolution: {integrity: sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==}
whatwg-fetch@3.6.20:
resolution: {integrity: sha512-EqhiFU6daOA8kpjOWTL0olhVOF3i7OrFzSYiGsEMB8GcXS+RrzauAERX65xMeNWVqxA6HXH2m69Z9LaKKdisfg==}
whatwg-url@5.0.0:
resolution: {integrity: sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==}
@@ -7873,91 +7827,68 @@ packages:
snapshots:
'@ai-sdk/anthropic@1.2.12(zod@3.25.32)':
'@ai-sdk/anthropic@2.0.5(zod@3.25.32)':
dependencies:
'@ai-sdk/provider': 1.1.3
'@ai-sdk/provider-utils': 2.2.8(zod@3.25.32)
'@ai-sdk/provider': 2.0.0
'@ai-sdk/provider-utils': 3.0.4(zod@3.25.32)
zod: 3.25.32
'@ai-sdk/azure@1.3.23(zod@3.25.32)':
'@ai-sdk/azure@2.0.16(zod@3.25.32)':
dependencies:
'@ai-sdk/openai': 1.3.22(zod@3.25.32)
'@ai-sdk/provider': 1.1.3
'@ai-sdk/provider-utils': 2.2.8(zod@3.25.32)
'@ai-sdk/openai': 2.0.16(zod@3.25.32)
'@ai-sdk/provider': 2.0.0
'@ai-sdk/provider-utils': 3.0.4(zod@3.25.32)
zod: 3.25.32
'@ai-sdk/cohere@1.2.10(zod@3.25.32)':
'@ai-sdk/cohere@2.0.4(zod@3.25.32)':
dependencies:
'@ai-sdk/provider': 1.1.3
'@ai-sdk/provider-utils': 2.2.8(zod@3.25.32)
'@ai-sdk/provider': 2.0.0
'@ai-sdk/provider-utils': 3.0.4(zod@3.25.32)
zod: 3.25.32
'@ai-sdk/deepinfra@0.0.4(zod@3.25.32)':
'@ai-sdk/deepinfra@1.0.10(zod@3.25.32)':
dependencies:
'@ai-sdk/openai-compatible': 0.0.13(zod@3.25.32)
'@ai-sdk/provider': 1.0.3
'@ai-sdk/provider-utils': 2.0.5(zod@3.25.32)
'@ai-sdk/openai-compatible': 1.0.10(zod@3.25.32)
'@ai-sdk/provider': 2.0.0
'@ai-sdk/provider-utils': 3.0.4(zod@3.25.32)
zod: 3.25.32
'@ai-sdk/mistral@1.2.8(zod@3.25.32)':
'@ai-sdk/gateway@1.0.8(zod@3.25.32)':
dependencies:
'@ai-sdk/provider': 1.1.3
'@ai-sdk/provider-utils': 2.2.8(zod@3.25.32)
'@ai-sdk/provider': 2.0.0
'@ai-sdk/provider-utils': 3.0.4(zod@3.25.32)
zod: 3.25.32
'@ai-sdk/openai-compatible@0.0.13(zod@3.25.32)':
'@ai-sdk/mistral@2.0.7(zod@3.25.32)':
dependencies:
'@ai-sdk/provider': 1.0.3
'@ai-sdk/provider-utils': 2.0.5(zod@3.25.32)
'@ai-sdk/provider': 2.0.0
'@ai-sdk/provider-utils': 3.0.4(zod@3.25.32)
zod: 3.25.32
'@ai-sdk/openai@1.3.22(zod@3.25.32)':
'@ai-sdk/openai-compatible@1.0.10(zod@3.25.32)':
dependencies:
'@ai-sdk/provider': 1.1.3
'@ai-sdk/provider-utils': 2.2.8(zod@3.25.32)
'@ai-sdk/provider': 2.0.0
'@ai-sdk/provider-utils': 3.0.4(zod@3.25.32)
zod: 3.25.32
'@ai-sdk/provider-utils@2.0.5(zod@3.25.32)':
'@ai-sdk/openai@2.0.16(zod@3.25.32)':
dependencies:
'@ai-sdk/provider': 1.0.3
eventsource-parser: 3.0.2
nanoid: 3.3.11
secure-json-parse: 2.7.0
optionalDependencies:
'@ai-sdk/provider': 2.0.0
'@ai-sdk/provider-utils': 3.0.4(zod@3.25.32)
zod: 3.25.32
'@ai-sdk/provider-utils@2.2.8(zod@3.25.32)':
'@ai-sdk/provider-utils@3.0.4(zod@3.25.32)':
dependencies:
'@ai-sdk/provider': 1.1.3
nanoid: 3.3.11
secure-json-parse: 2.7.0
zod: 3.25.32
'@ai-sdk/provider@1.0.3':
dependencies:
json-schema: 0.4.0
'@ai-sdk/provider@1.1.3':
dependencies:
json-schema: 0.4.0
'@ai-sdk/react@1.2.12(react@18.2.0)(zod@3.25.32)':
dependencies:
'@ai-sdk/provider-utils': 2.2.8(zod@3.25.32)
'@ai-sdk/ui-utils': 1.2.11(zod@3.25.32)
react: 18.2.0
swr: 2.3.3(react@18.2.0)
throttleit: 2.1.0
optionalDependencies:
zod: 3.25.32
'@ai-sdk/ui-utils@1.2.11(zod@3.25.32)':
dependencies:
'@ai-sdk/provider': 1.1.3
'@ai-sdk/provider-utils': 2.2.8(zod@3.25.32)
'@ai-sdk/provider': 2.0.0
'@standard-schema/spec': 1.0.0
eventsource-parser: 3.0.5
zod: 3.25.32
zod-to-json-schema: 3.24.5(zod@3.25.32)
'@ai-sdk/provider@2.0.0':
dependencies:
json-schema: 0.4.0
'@alloc/quick-lru@5.2.0': {}
'@babel/code-frame@7.27.1':
@@ -10767,6 +10698,8 @@ snapshots:
'@sindresorhus/is@5.6.0': {}
'@standard-schema/spec@1.0.0': {}
'@stepperize/react@4.0.1(react-dom@18.2.0(react@18.2.0))(react@18.2.0)':
dependencies:
react: 18.2.0
@@ -11257,8 +11190,6 @@ snapshots:
dependencies:
'@types/ms': 2.1.0
'@types/diff-match-patch@1.0.36': {}
'@types/docker-modem@3.0.6':
dependencies:
'@types/node': 20.17.51
@@ -11347,7 +11278,7 @@ snapshots:
'@types/pg@8.6.1':
dependencies:
'@types/node': 20.17.51
'@types/node': 18.19.104
pg-protocol: 1.10.3
pg-types: 2.2.0
@@ -11534,17 +11465,22 @@ snapshots:
clean-stack: 4.2.0
indent-string: 5.0.0
ai@4.3.16(react@18.2.0)(zod@3.25.32):
ai-sdk-ollama@0.5.1(zod@3.25.32):
dependencies:
'@ai-sdk/provider': 1.1.3
'@ai-sdk/provider-utils': 2.2.8(zod@3.25.32)
'@ai-sdk/react': 1.2.12(react@18.2.0)(zod@3.25.32)
'@ai-sdk/ui-utils': 1.2.11(zod@3.25.32)
'@ai-sdk/provider': 2.0.0
'@ai-sdk/provider-utils': 3.0.4(zod@3.25.32)
ai: 5.0.17(zod@3.25.32)
ollama: 0.5.17
transitivePeerDependencies:
- zod
ai@5.0.17(zod@3.25.32):
dependencies:
'@ai-sdk/gateway': 1.0.8(zod@3.25.32)
'@ai-sdk/provider': 2.0.0
'@ai-sdk/provider-utils': 3.0.4(zod@3.25.32)
'@opentelemetry/api': 1.9.0
jsondiffpatch: 0.6.0
zod: 3.25.32
optionalDependencies:
react: 18.2.0
ajv@8.17.1:
dependencies:
@@ -12181,8 +12117,6 @@ snapshots:
didyoumean@1.2.2: {}
diff-match-patch@1.0.5: {}
diff-sequences@29.6.3: {}
dijkstrajs@1.0.3: {}
@@ -12469,7 +12403,7 @@ snapshots:
events@3.3.0: {}
eventsource-parser@3.0.2: {}
eventsource-parser@3.0.5: {}
execa@8.0.1:
dependencies:
@@ -13088,12 +13022,6 @@ snapshots:
json-stringify-safe@5.0.1: {}
jsondiffpatch@0.6.0:
dependencies:
'@types/diff-match-patch': 1.0.36
chalk: 5.4.1
diff-match-patch: 1.0.5
jsonparse@1.3.1: {}
jsonwebtoken@9.0.2:
@@ -13895,13 +13823,9 @@ snapshots:
'@octokit/request-error': 5.1.1
'@octokit/types': 12.6.0
ollama-ai-provider@1.2.0(zod@3.25.32):
ollama@0.5.17:
dependencies:
'@ai-sdk/provider': 1.1.3
'@ai-sdk/provider-utils': 2.2.8(zod@3.25.32)
partial-json: 0.1.7
optionalDependencies:
zod: 3.25.32
whatwg-fetch: 3.6.20
on-exit-leak-free@2.1.2: {}
@@ -13994,8 +13918,6 @@ snapshots:
parseurl@1.3.3: {}
partial-json@0.1.7: {}
path-exists@4.0.0: {}
path-exists@5.0.0: {}
@@ -14957,12 +14879,6 @@ snapshots:
- '@types/react'
- debug
swr@2.3.3(react@18.2.0):
dependencies:
dequal: 2.0.3
react: 18.2.0
use-sync-external-store: 1.5.0(react@18.2.0)
symbol-observable@1.2.0: {}
tailwind-merge@2.6.0: {}
@@ -15054,8 +14970,6 @@ snapshots:
dependencies:
real-require: 0.2.0
throttleit@2.1.0: {}
through@2.3.8: {}
tiny-invariant@1.3.3: {}
@@ -15359,6 +15273,8 @@ snapshots:
webidl-conversions@3.0.1: {}
whatwg-fetch@3.6.20: {}
whatwg-url@5.0.0:
dependencies:
tr46: 0.0.3