mirror of
https://github.com/fosrl/pangolin.git
synced 2026-09-18 08:39:51 +02:00
add alert warning for configure coding agents
This commit is contained in:
+5
-1
@@ -1786,12 +1786,16 @@
|
|||||||
"aiClientConfigDescriptionCodex": "OpenAI's agentic coding tool for the terminal.",
|
"aiClientConfigDescriptionCodex": "OpenAI's agentic coding tool for the terminal.",
|
||||||
"aiClientConfigDescriptionOpencode": "Open source terminal coding agent.",
|
"aiClientConfigDescriptionOpencode": "Open source terminal coding agent.",
|
||||||
"aiClientConfigDescriptionCursor": "AI code editor built on VS Code.",
|
"aiClientConfigDescriptionCursor": "AI code editor built on VS Code.",
|
||||||
|
"aiClientConfigSetup": "Setup",
|
||||||
"aiClientConfigTabCli": "Automatic (CLI)",
|
"aiClientConfigTabCli": "Automatic (CLI)",
|
||||||
"aiClientConfigTabManual": "Manual Configuration",
|
"aiClientConfigTabManual": "Manual Configuration",
|
||||||
|
"aiClientConfigPreset": "Configuration",
|
||||||
|
"aiClientConfigStep": "Step {number}",
|
||||||
"aiClientConfigEndpointPlaceholder": "https://example.resource.url.com",
|
"aiClientConfigEndpointPlaceholder": "https://example.resource.url.com",
|
||||||
|
"aiClientConfigPlaceholderWarning": "This snippet includes example values, such as a model name or resource URL. Replace them with your own before using it.",
|
||||||
"aiClientConfigRevealError": "Could not load your API key.",
|
"aiClientConfigRevealError": "Could not load your API key.",
|
||||||
"aiClientConfigRevealRetry": "Try again",
|
"aiClientConfigRevealRetry": "Try again",
|
||||||
"resourceGeneralAiClientConfigLink": "See how to configure access to this resource in common clients like Claude Code and OpenCode",
|
"resourceGeneralAiClientConfigDescription": "This resource can be used from common clients like Claude Code and OpenCode. <configLink>Learn more</configLink>",
|
||||||
"aiProvidersTitle": "AI Providers",
|
"aiProvidersTitle": "AI Providers",
|
||||||
"aiProvidersDescription": "Connect model providers for AI workloads in this organization",
|
"aiProvidersDescription": "Connect model providers for AI workloads in this organization",
|
||||||
"aiProvidersBannerTitle": "Connect Model Providers",
|
"aiProvidersBannerTitle": "Connect Model Providers",
|
||||||
|
|||||||
@@ -28,6 +28,7 @@ import { createGeneralFormSchema } from "@app/lib/privateResourceForm";
|
|||||||
import { zodResolver } from "@hookform/resolvers/zod";
|
import { zodResolver } from "@hookform/resolvers/zod";
|
||||||
import { useTranslations } from "next-intl";
|
import { useTranslations } from "next-intl";
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
|
import { ExternalLink } from "lucide-react";
|
||||||
import { useActionState, useMemo } from "react";
|
import { useActionState, useMemo } from "react";
|
||||||
import { useForm } from "react-hook-form";
|
import { useForm } from "react-hook-form";
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
@@ -70,17 +71,26 @@ export default function PrivateResourceGeneralPage() {
|
|||||||
</SettingsSectionTitle>
|
</SettingsSectionTitle>
|
||||||
<SettingsSectionDescription>
|
<SettingsSectionDescription>
|
||||||
{t("privateResourceGeneralDescription")}
|
{t("privateResourceGeneralDescription")}
|
||||||
|
{siteResource.mode === "inference" ? (
|
||||||
|
<>
|
||||||
|
{" "}
|
||||||
|
{t.rich(
|
||||||
|
"resourceGeneralAiClientConfigDescription",
|
||||||
|
{
|
||||||
|
configLink: (chunks) => (
|
||||||
|
<Link
|
||||||
|
href={`/${siteResource.orgId}?openResource=${encodeURIComponent(siteResource.niceId)}&openResourceQuery=${encodeURIComponent(siteResource.name)}`}
|
||||||
|
className="text-primary hover:underline inline-flex items-center gap-1"
|
||||||
|
>
|
||||||
|
{chunks}
|
||||||
|
<ExternalLink className="size-3.5 shrink-0" />
|
||||||
|
</Link>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
) : null}
|
||||||
</SettingsSectionDescription>
|
</SettingsSectionDescription>
|
||||||
{siteResource.mode === "inference" ? (
|
|
||||||
<p className="text-sm pt-1">
|
|
||||||
<Link
|
|
||||||
href={`/${siteResource.orgId}?openResource=${encodeURIComponent(siteResource.niceId)}&openResourceQuery=${encodeURIComponent(siteResource.name)}`}
|
|
||||||
className="text-primary hover:underline"
|
|
||||||
>
|
|
||||||
{t("resourceGeneralAiClientConfigLink")}
|
|
||||||
</Link>
|
|
||||||
</p>
|
|
||||||
) : null}
|
|
||||||
</SettingsSectionHeader>
|
</SettingsSectionHeader>
|
||||||
|
|
||||||
<SettingsSectionBody>
|
<SettingsSectionBody>
|
||||||
|
|||||||
@@ -50,6 +50,7 @@ import { useOrgContext } from "@app/hooks/useOrgContext";
|
|||||||
import { orgQueries } from "@app/lib/queries";
|
import { orgQueries } from "@app/lib/queries";
|
||||||
import { useQuery } from "@tanstack/react-query";
|
import { useQuery } from "@tanstack/react-query";
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
|
import { ExternalLink } from "lucide-react";
|
||||||
import { build } from "@server/build";
|
import { build } from "@server/build";
|
||||||
import { TierFeature } from "@server/lib/billing/tierMatrix";
|
import { TierFeature } from "@server/lib/billing/tierMatrix";
|
||||||
import { usePaidStatus } from "@app/hooks/usePaidStatus";
|
import { usePaidStatus } from "@app/hooks/usePaidStatus";
|
||||||
@@ -255,17 +256,26 @@ export default function GeneralForm() {
|
|||||||
</SettingsSectionTitle>
|
</SettingsSectionTitle>
|
||||||
<SettingsSectionDescription>
|
<SettingsSectionDescription>
|
||||||
{t("resourceGeneralDescription")}
|
{t("resourceGeneralDescription")}
|
||||||
|
{resource.mode === "inference" ? (
|
||||||
|
<>
|
||||||
|
{" "}
|
||||||
|
{t.rich(
|
||||||
|
"resourceGeneralAiClientConfigDescription",
|
||||||
|
{
|
||||||
|
configLink: (chunks) => (
|
||||||
|
<Link
|
||||||
|
href={`/${resource.orgId}?openResource=${encodeURIComponent(resource.niceId)}&openResourceQuery=${encodeURIComponent(resource.name)}`}
|
||||||
|
className="text-primary hover:underline inline-flex items-center gap-1"
|
||||||
|
>
|
||||||
|
{chunks}
|
||||||
|
<ExternalLink className="size-3.5 shrink-0" />
|
||||||
|
</Link>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
) : null}
|
||||||
</SettingsSectionDescription>
|
</SettingsSectionDescription>
|
||||||
{resource.mode === "inference" ? (
|
|
||||||
<p className="text-sm pt-1">
|
|
||||||
<Link
|
|
||||||
href={`/${resource.orgId}?openResource=${encodeURIComponent(resource.niceId)}&openResourceQuery=${encodeURIComponent(resource.name)}`}
|
|
||||||
className="text-primary hover:underline"
|
|
||||||
>
|
|
||||||
{t("resourceGeneralAiClientConfigLink")}
|
|
||||||
</Link>
|
|
||||||
</p>
|
|
||||||
) : null}
|
|
||||||
</SettingsSectionHeader>
|
</SettingsSectionHeader>
|
||||||
|
|
||||||
<SettingsSectionBody>
|
<SettingsSectionBody>
|
||||||
|
|||||||
@@ -1,25 +1,13 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { AiConfigCodeBlock } from "@app/components/ai-client-config/AiConfigCodeBlock";
|
import { AiConfigBlocks } from "@app/components/ai-client-config/AiConfigBlocks";
|
||||||
import { Button } from "@app/components/ui/button";
|
import { Button } from "@app/components/ui/button";
|
||||||
import {
|
import {
|
||||||
Collapsible,
|
Collapsible,
|
||||||
CollapsibleContent,
|
CollapsibleContent,
|
||||||
CollapsibleTrigger
|
CollapsibleTrigger
|
||||||
} from "@app/components/ui/collapsible";
|
} from "@app/components/ui/collapsible";
|
||||||
import {
|
import { OptionSelect } from "@app/components/OptionSelect";
|
||||||
Select,
|
|
||||||
SelectContent,
|
|
||||||
SelectItem,
|
|
||||||
SelectTrigger,
|
|
||||||
SelectValue
|
|
||||||
} from "@app/components/ui/select";
|
|
||||||
import {
|
|
||||||
Tabs,
|
|
||||||
TabsContent,
|
|
||||||
TabsList,
|
|
||||||
TabsTrigger
|
|
||||||
} from "@app/components/ui/tabs";
|
|
||||||
import type {
|
import type {
|
||||||
AiClientAuthInput,
|
AiClientAuthInput,
|
||||||
AiClientId,
|
AiClientId,
|
||||||
@@ -43,6 +31,8 @@ type AiClientConfigCardProps = {
|
|||||||
resourceNiceId?: string;
|
resourceNiceId?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
type SetupMode = "cli" | "manual";
|
||||||
|
|
||||||
export function AiClientConfigCard({
|
export function AiClientConfigCard({
|
||||||
clientId,
|
clientId,
|
||||||
name,
|
name,
|
||||||
@@ -57,6 +47,7 @@ export function AiClientConfigCard({
|
|||||||
const [resolvedIconSrc, setResolvedIconSrc] = useState(iconSrc.light);
|
const [resolvedIconSrc, setResolvedIconSrc] = useState(iconSrc.light);
|
||||||
const [open, setOpen] = useState(false);
|
const [open, setOpen] = useState(false);
|
||||||
const [presetId, setPresetId] = useState<AiClientPresetId>("default");
|
const [presetId, setPresetId] = useState<AiClientPresetId>("default");
|
||||||
|
const [setupMode, setSetupMode] = useState<SetupMode>("cli");
|
||||||
const [revealedKey, setRevealedKey] = useState<string | null>(null);
|
const [revealedKey, setRevealedKey] = useState<string | null>(null);
|
||||||
const [revealing, setRevealing] = useState(false);
|
const [revealing, setRevealing] = useState(false);
|
||||||
const [revealError, setRevealError] = useState(false);
|
const [revealError, setRevealError] = useState(false);
|
||||||
@@ -120,29 +111,24 @@ export function AiClientConfigCard({
|
|||||||
const manualContent = guide ? (
|
const manualContent = guide ? (
|
||||||
<div className="min-w-0 space-y-4">
|
<div className="min-w-0 space-y-4">
|
||||||
{guide.presets.length > 1 ? (
|
{guide.presets.length > 1 ? (
|
||||||
<Select
|
<OptionSelect<AiClientPresetId>
|
||||||
value={presetId}
|
label={t("aiClientConfigPreset")}
|
||||||
onValueChange={(value) =>
|
options={guide.presets.map((p) => ({
|
||||||
setPresetId(value as AiClientPresetId)
|
value: p.id,
|
||||||
}
|
label: p.label
|
||||||
>
|
}))}
|
||||||
<SelectTrigger size="sm" className="max-w-xs">
|
value={preset?.id ?? presetId}
|
||||||
<SelectValue />
|
onChange={setPresetId}
|
||||||
</SelectTrigger>
|
cols={2}
|
||||||
<SelectContent>
|
/>
|
||||||
{guide.presets.map((p) => (
|
) : null}
|
||||||
<SelectItem key={p.id} value={p.id}>
|
{preset ? (
|
||||||
{p.label}
|
<AiConfigBlocks
|
||||||
</SelectItem>
|
key={preset.id}
|
||||||
))}
|
blocks={preset.blocks}
|
||||||
</SelectContent>
|
relation={preset.relation}
|
||||||
</Select>
|
/>
|
||||||
) : null}
|
) : null}
|
||||||
<div className="grid min-w-0 gap-4">
|
|
||||||
{preset?.blocks.map((block) => (
|
|
||||||
<AiConfigCodeBlock key={block.id} block={block} />
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
) : null;
|
) : null;
|
||||||
|
|
||||||
@@ -191,35 +177,32 @@ export function AiClientConfigCard({
|
|||||||
) : null}
|
) : null}
|
||||||
{guide ? (
|
{guide ? (
|
||||||
guide.cli ? (
|
guide.cli ? (
|
||||||
<Tabs defaultValue="cli">
|
<div className="min-w-0 space-y-4">
|
||||||
<TabsList>
|
<OptionSelect<SetupMode>
|
||||||
<TabsTrigger value="cli">
|
label={t("aiClientConfigSetup")}
|
||||||
{t("aiClientConfigTabCli")}
|
options={[
|
||||||
</TabsTrigger>
|
{
|
||||||
<TabsTrigger value="manual">
|
value: "cli",
|
||||||
{t("aiClientConfigTabManual")}
|
label: t("aiClientConfigTabCli")
|
||||||
</TabsTrigger>
|
},
|
||||||
</TabsList>
|
{
|
||||||
<TabsContent
|
value: "manual",
|
||||||
value="cli"
|
label: t("aiClientConfigTabManual")
|
||||||
className="grid min-w-0 gap-4 mt-4"
|
}
|
||||||
>
|
]}
|
||||||
<AiConfigCodeBlock
|
value={setupMode}
|
||||||
block={guide.cli.configure}
|
onChange={setSetupMode}
|
||||||
|
cols={2}
|
||||||
|
/>
|
||||||
|
{setupMode === "cli" ? (
|
||||||
|
<AiConfigBlocks
|
||||||
|
blocks={guide.cli}
|
||||||
|
relation="options"
|
||||||
/>
|
/>
|
||||||
{guide.cli.configureWithKey ? (
|
) : (
|
||||||
<AiConfigCodeBlock
|
manualContent
|
||||||
block={guide.cli.configureWithKey}
|
)}
|
||||||
/>
|
</div>
|
||||||
) : null}
|
|
||||||
</TabsContent>
|
|
||||||
<TabsContent
|
|
||||||
value="manual"
|
|
||||||
className="min-w-0 mt-4"
|
|
||||||
>
|
|
||||||
{manualContent}
|
|
||||||
</TabsContent>
|
|
||||||
</Tabs>
|
|
||||||
) : (
|
) : (
|
||||||
manualContent
|
manualContent
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -0,0 +1,72 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
|
import { AiConfigCodeBlock } from "@app/components/ai-client-config/AiConfigCodeBlock";
|
||||||
|
import {
|
||||||
|
OptionSelect,
|
||||||
|
type OptionSelectOption
|
||||||
|
} from "@app/components/OptionSelect";
|
||||||
|
import type { AiConfigBlock, AiConfigRelation } from "@app/lib/aiClientConfig";
|
||||||
|
import { useTranslations } from "next-intl";
|
||||||
|
import { useEffect, useMemo, useState } from "react";
|
||||||
|
|
||||||
|
type AiConfigBlocksProps = {
|
||||||
|
blocks: AiConfigBlock[];
|
||||||
|
relation: AiConfigRelation;
|
||||||
|
};
|
||||||
|
|
||||||
|
export function AiConfigBlocks({ blocks, relation }: AiConfigBlocksProps) {
|
||||||
|
const t = useTranslations();
|
||||||
|
const showPicker = relation === "options" && blocks.length > 1;
|
||||||
|
const [selectedId, setSelectedId] = useState(blocks[0]?.id ?? "");
|
||||||
|
|
||||||
|
const methodOptions: OptionSelectOption<string>[] = useMemo(
|
||||||
|
() =>
|
||||||
|
blocks.map((block) => ({
|
||||||
|
value: block.id,
|
||||||
|
label: block.label
|
||||||
|
})),
|
||||||
|
[blocks]
|
||||||
|
);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!blocks.some((block) => block.id === selectedId)) {
|
||||||
|
setSelectedId(blocks[0]?.id ?? "");
|
||||||
|
}
|
||||||
|
}, [blocks, selectedId]);
|
||||||
|
|
||||||
|
if (blocks.length === 0) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (showPicker) {
|
||||||
|
const selected =
|
||||||
|
blocks.find((block) => block.id === selectedId) ?? blocks[0];
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="min-w-0 space-y-4">
|
||||||
|
<OptionSelect
|
||||||
|
label={t("method")}
|
||||||
|
options={methodOptions}
|
||||||
|
value={selected.id}
|
||||||
|
onChange={setSelectedId}
|
||||||
|
cols={2}
|
||||||
|
/>
|
||||||
|
<AiConfigCodeBlock block={selected} hideLabel />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const numberSteps = relation === "steps" && blocks.length > 1;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="grid min-w-0 gap-4">
|
||||||
|
{blocks.map((block, index) => (
|
||||||
|
<AiConfigCodeBlock
|
||||||
|
key={block.id}
|
||||||
|
block={block}
|
||||||
|
step={numberSteps ? index + 1 : undefined}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -1,23 +1,53 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import CopyTextBox from "@app/components/CopyTextBox";
|
import CopyTextBox from "@app/components/CopyTextBox";
|
||||||
import type { AiConfigBlock } from "@app/lib/aiClientConfig";
|
import { Alert, AlertDescription } from "@app/components/ui/alert";
|
||||||
|
import {
|
||||||
|
aiConfigBlockHasPlaceholders,
|
||||||
|
type AiConfigBlock
|
||||||
|
} from "@app/lib/aiClientConfig";
|
||||||
import { cn } from "@app/lib/cn";
|
import { cn } from "@app/lib/cn";
|
||||||
|
import { AlertCircle } from "lucide-react";
|
||||||
|
import { useTranslations } from "next-intl";
|
||||||
|
|
||||||
|
type AiConfigCodeBlockProps = {
|
||||||
|
block: AiConfigBlock;
|
||||||
|
step?: number;
|
||||||
|
hideLabel?: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
|
export function AiConfigCodeBlock({
|
||||||
|
block,
|
||||||
|
step,
|
||||||
|
hideLabel = false
|
||||||
|
}: AiConfigCodeBlockProps) {
|
||||||
|
const t = useTranslations();
|
||||||
|
const label =
|
||||||
|
step != null
|
||||||
|
? `${t("aiClientConfigStep", { number: step })}: ${block.label}`
|
||||||
|
: block.label;
|
||||||
|
|
||||||
export function AiConfigCodeBlock({ block }: { block: AiConfigBlock }) {
|
|
||||||
return (
|
return (
|
||||||
<div className="min-w-0 space-y-1.5">
|
<div className="min-w-0 space-y-1.5">
|
||||||
<p className="font-mono text-xs text-muted-foreground">
|
{!hideLabel ? (
|
||||||
{block.label}
|
<p className="font-mono text-xs text-muted-foreground">
|
||||||
</p>
|
{label}
|
||||||
|
</p>
|
||||||
|
) : null}
|
||||||
<div
|
<div
|
||||||
className={cn(
|
className={cn(
|
||||||
"min-w-0",
|
"min-w-0",
|
||||||
block.kind === "steps"
|
block.kind !== "steps" && "[&_code]:font-mono"
|
||||||
? "[&_pre]:text-sm"
|
|
||||||
: "[&_pre]:text-xs [&_code]:font-mono"
|
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
|
{aiConfigBlockHasPlaceholders(block) ? (
|
||||||
|
<Alert variant="neutral" className="mb-3">
|
||||||
|
<AlertCircle className="h-4 w-4" />
|
||||||
|
<AlertDescription>
|
||||||
|
{t("aiClientConfigPlaceholderWarning")}
|
||||||
|
</AlertDescription>
|
||||||
|
</Alert>
|
||||||
|
) : null}
|
||||||
<CopyTextBox
|
<CopyTextBox
|
||||||
text={block.displayText}
|
text={block.displayText}
|
||||||
wrapText={block.kind === "steps"}
|
wrapText={block.kind === "steps"}
|
||||||
|
|||||||
+62
-26
@@ -21,25 +21,26 @@ export type AiConfigBlock = {
|
|||||||
label: string;
|
label: string;
|
||||||
kind?: "code" | "steps";
|
kind?: "code" | "steps";
|
||||||
displayText: string;
|
displayText: string;
|
||||||
|
/** True when the snippet includes example values the user must replace. */
|
||||||
|
hasPlaceholders?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type AiClientPresetId = "default" | "bedrock" | "vertex" | "kimi";
|
export type AiClientPresetId = "default" | "bedrock" | "vertex" | "kimi";
|
||||||
|
|
||||||
|
export type AiConfigRelation = "options" | "steps";
|
||||||
|
|
||||||
export type AiConfigPreset = {
|
export type AiConfigPreset = {
|
||||||
id: AiClientPresetId;
|
id: AiClientPresetId;
|
||||||
label: string;
|
label: string;
|
||||||
|
relation: AiConfigRelation;
|
||||||
blocks: AiConfigBlock[];
|
blocks: AiConfigBlock[];
|
||||||
};
|
};
|
||||||
|
|
||||||
export type AiCliCommands = {
|
|
||||||
configure: AiConfigBlock;
|
|
||||||
configureWithKey?: AiConfigBlock;
|
|
||||||
};
|
|
||||||
|
|
||||||
export type AiClientGuide = {
|
export type AiClientGuide = {
|
||||||
id: AiClientId;
|
id: AiClientId;
|
||||||
name: string;
|
name: string;
|
||||||
cli: AiCliCommands | null;
|
/** Alternative CLI commands. Empty/null means this client has no CLI setup. */
|
||||||
|
cli: AiConfigBlock[] | null;
|
||||||
presets: AiConfigPreset[];
|
presets: AiConfigPreset[];
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -52,19 +53,33 @@ function block(
|
|||||||
label: string,
|
label: string,
|
||||||
build: (keyValue: string) => string,
|
build: (keyValue: string) => string,
|
||||||
auth: AiClientAuth,
|
auth: AiClientAuth,
|
||||||
kind: "code" | "steps" = "code"
|
kind: "code" | "steps" = "code",
|
||||||
|
hasPlaceholders = false
|
||||||
): AiConfigBlock {
|
): AiConfigBlock {
|
||||||
return { id, label, kind, displayText: build(keyValue(auth)) };
|
return {
|
||||||
|
id,
|
||||||
|
label,
|
||||||
|
kind,
|
||||||
|
displayText: build(keyValue(auth)),
|
||||||
|
hasPlaceholders
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const EXAMPLE_RESOURCE_HOST = "example.resource.url.com";
|
||||||
|
|
||||||
|
export function aiConfigBlockHasPlaceholders(block: AiConfigBlock): boolean {
|
||||||
|
return (
|
||||||
|
block.hasPlaceholders === true ||
|
||||||
|
block.displayText.includes(EXAMPLE_RESOURCE_HOST)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function buildCli(
|
function buildCli(
|
||||||
clientArg: "claude" | "codex" | "opencode",
|
clientArg: "claude" | "codex" | "opencode",
|
||||||
auth: AiClientAuth,
|
auth: AiClientAuth,
|
||||||
resourceNiceId?: string
|
resourceNiceId?: string
|
||||||
): AiCliCommands {
|
): AiConfigBlock[] {
|
||||||
const resourceFlag = resourceNiceId
|
const resourceFlag = resourceNiceId ? ` --resource ${resourceNiceId}` : "";
|
||||||
? ` --resource ${resourceNiceId}`
|
|
||||||
: "";
|
|
||||||
|
|
||||||
const configure: AiConfigBlock = {
|
const configure: AiConfigBlock = {
|
||||||
id: `cli-configure-${clientArg}`,
|
id: `cli-configure-${clientArg}`,
|
||||||
@@ -73,17 +88,17 @@ function buildCli(
|
|||||||
};
|
};
|
||||||
|
|
||||||
if (auth.mode !== "keyed") {
|
if (auth.mode !== "keyed") {
|
||||||
return { configure };
|
return [configure];
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return [
|
||||||
configure,
|
configure,
|
||||||
configureWithKey: {
|
{
|
||||||
id: `cli-configure-key-${clientArg}`,
|
id: `cli-configure-key-${clientArg}`,
|
||||||
label: "Configure with an API key",
|
label: "Configure with an API key",
|
||||||
displayText: `pangolin configure ${clientArg} ${auth.key}${resourceFlag}`
|
displayText: `pangolin configure ${clientArg} ${auth.key}${resourceFlag}`
|
||||||
}
|
}
|
||||||
};
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
function buildClaudeGuide(
|
function buildClaudeGuide(
|
||||||
@@ -132,7 +147,9 @@ function buildClaudeGuide(
|
|||||||
" }",
|
" }",
|
||||||
"}"
|
"}"
|
||||||
].join("\n"),
|
].join("\n"),
|
||||||
auth
|
auth,
|
||||||
|
"code",
|
||||||
|
true
|
||||||
);
|
);
|
||||||
|
|
||||||
const vertexSettings = block(
|
const vertexSettings = block(
|
||||||
@@ -150,7 +167,9 @@ function buildClaudeGuide(
|
|||||||
" }",
|
" }",
|
||||||
"}"
|
"}"
|
||||||
].join("\n"),
|
].join("\n"),
|
||||||
auth
|
auth,
|
||||||
|
"code",
|
||||||
|
true
|
||||||
);
|
);
|
||||||
|
|
||||||
const kimiSettings = block(
|
const kimiSettings = block(
|
||||||
@@ -171,7 +190,9 @@ function buildClaudeGuide(
|
|||||||
" }",
|
" }",
|
||||||
"}"
|
"}"
|
||||||
].join("\n"),
|
].join("\n"),
|
||||||
auth
|
auth,
|
||||||
|
"code",
|
||||||
|
true
|
||||||
);
|
);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
@@ -182,21 +203,25 @@ function buildClaudeGuide(
|
|||||||
{
|
{
|
||||||
id: "default",
|
id: "default",
|
||||||
label: "Default (Anthropic)",
|
label: "Default (Anthropic)",
|
||||||
|
relation: "options",
|
||||||
blocks: [defaultSettings, defaultShell]
|
blocks: [defaultSettings, defaultShell]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: "bedrock",
|
id: "bedrock",
|
||||||
label: "Amazon Bedrock",
|
label: "Amazon Bedrock",
|
||||||
|
relation: "options",
|
||||||
blocks: [bedrockSettings]
|
blocks: [bedrockSettings]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: "vertex",
|
id: "vertex",
|
||||||
label: "Google Vertex AI",
|
label: "Google Vertex AI",
|
||||||
|
relation: "options",
|
||||||
blocks: [vertexSettings]
|
blocks: [vertexSettings]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: "kimi",
|
id: "kimi",
|
||||||
label: "Kimi K2 (Moonshot AI)",
|
label: "Kimi K2 (Moonshot AI)",
|
||||||
|
relation: "options",
|
||||||
blocks: [kimiSettings]
|
blocks: [kimiSettings]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
@@ -219,7 +244,9 @@ function buildCodexGuide(
|
|||||||
'name = "Pangolin AI Gateway"',
|
'name = "Pangolin AI Gateway"',
|
||||||
`base_url = "${endpoint}/v1"`,
|
`base_url = "${endpoint}/v1"`,
|
||||||
'wire_api = "responses"',
|
'wire_api = "responses"',
|
||||||
...(auth.mode === "keyed" ? ['env_key = "PANGOLIN_API_KEY"'] : [])
|
...(auth.mode === "keyed"
|
||||||
|
? ['env_key = "PANGOLIN_API_KEY"']
|
||||||
|
: [])
|
||||||
].join("\n"),
|
].join("\n"),
|
||||||
auth
|
auth
|
||||||
);
|
);
|
||||||
@@ -242,6 +269,7 @@ function buildCodexGuide(
|
|||||||
{
|
{
|
||||||
id: "default",
|
id: "default",
|
||||||
label: "Default",
|
label: "Default",
|
||||||
|
relation: "steps",
|
||||||
blocks: shell ? [settings, shell] : [settings]
|
blocks: shell ? [settings, shell] : [settings]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
@@ -255,7 +283,7 @@ function buildOpencodeGuide(
|
|||||||
): AiClientGuide {
|
): AiClientGuide {
|
||||||
const config = block(
|
const config = block(
|
||||||
"opencode-config",
|
"opencode-config",
|
||||||
"Step 1: opencode.json",
|
"opencode.json",
|
||||||
() =>
|
() =>
|
||||||
[
|
[
|
||||||
"{",
|
"{",
|
||||||
@@ -279,11 +307,16 @@ function buildOpencodeGuide(
|
|||||||
|
|
||||||
const authFile = block(
|
const authFile = block(
|
||||||
"opencode-auth",
|
"opencode-auth",
|
||||||
"Step 2: auth.json",
|
"auth.json",
|
||||||
(key) =>
|
(key) =>
|
||||||
["{", ' "anthropic": {', ' "type": "api",', ` "key": "${key}"`, " }", "}"].join(
|
[
|
||||||
"\n"
|
"{",
|
||||||
),
|
' "anthropic": {',
|
||||||
|
' "type": "api",',
|
||||||
|
` "key": "${key}"`,
|
||||||
|
" }",
|
||||||
|
"}"
|
||||||
|
].join("\n"),
|
||||||
auth
|
auth
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -295,6 +328,7 @@ function buildOpencodeGuide(
|
|||||||
{
|
{
|
||||||
id: "default",
|
id: "default",
|
||||||
label: "Default",
|
label: "Default",
|
||||||
|
relation: "steps",
|
||||||
blocks: [config, authFile]
|
blocks: [config, authFile]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
@@ -316,7 +350,8 @@ function buildCursorGuide(endpoint: string, auth: AiClientAuth): AiClientGuide {
|
|||||||
"5. Add a custom model matching the model your Pangolin AI Gateway serves (e.g. claude-sonnet-4-6)."
|
"5. Add a custom model matching the model your Pangolin AI Gateway serves (e.g. claude-sonnet-4-6)."
|
||||||
].join("\n"),
|
].join("\n"),
|
||||||
auth,
|
auth,
|
||||||
"steps"
|
"steps",
|
||||||
|
true
|
||||||
);
|
);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
@@ -327,6 +362,7 @@ function buildCursorGuide(endpoint: string, auth: AiClientAuth): AiClientGuide {
|
|||||||
{
|
{
|
||||||
id: "default",
|
id: "default",
|
||||||
label: "Default",
|
label: "Default",
|
||||||
|
relation: "steps",
|
||||||
blocks: [steps]
|
blocks: [steps]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|||||||
Reference in New Issue
Block a user