modelforge 0.1.11

Provider-aware model target parsing primitives for archive tools
Documentation
# ModelForge Agent Guide

This guide describes the public API and integration boundary of ModelForge
0.1.11. ModelForge normalizes user-supplied model targets. It does not contact
providers or decide what should be downloaded.

The same text is available at runtime through the crate-root
`modelforge::AGENT_GUIDE` constant.

## Provider Identification

`ModelProvider` has three variants in stable `ModelProvider::ALL` and
`ModelProvider::all()` display order:

- `ModelProvider::Hf`: label `Hugging Face`, short label `HF`, slug `hf`
- `ModelProvider::Ollama`: label `Ollama`, short label `OLM`, slug `ollama`
- `ModelProvider::Civitai`: label `CivitAI`, short label `CIV`, slug `civitai`

`ModelProvider::from_slug` trims the input, removes spaces, tabs, line breaks,
hyphens, and underscores, and compares the remaining value without case
sensitivity. It accepts `hf` and `huggingface`; `ollama` and `olm`; and
`civitai`, `civit`, and `civ`. It returns `None` for an unknown provider. An
agent should reject or ask the user to resolve an unknown provider rather than
guessing one from the target text.

## Target Parsing

Use `parse_model_target_for_provider` whenever the provider is known. All
providers recognize these trimmed, case-insensitive presets:

- `top`, `trending`, and `popular` become `ModelTarget::Top`.
- `latest`, `newest`, and `new` become `ModelTarget::Latest`.

The remaining rules depend on the provider.

### Hugging Face

- A single nonempty path component such as `openai-community` becomes
  `ModelTarget::Org("openai-community")`.
- Two or more components such as `openai-community/gpt2` become
  `ModelTarget::Model("openai-community/gpt2")`. Components after the first
  two are ignored.
- ModelForge recognizes HTTP and HTTPS URLs on `huggingface.co`,
  `www.huggingface.co`, `hf.co`, and `www.hf.co`, with host-prefix matching
  performed without ASCII case sensitivity.
- A recognized URL with one path component becomes `Org`; one with at least
  two becomes `Model`. Its query and fragment are removed before path parsing.

`parse_model_target` applies these same Hugging Face-style rules but does not
recognize presets and has no provider-specific CivitAI or Ollama handling.

### Ollama

- A bare value such as `llama3` follows the generic path rule and becomes
  `ModelTarget::Org("llama3")`. In this crate, `Org` also represents an owner
  or search keyword; it does not assert that Ollama has an organization with
  that name.
- ModelForge recognizes HTTP and HTTPS model-page URLs under
  `ollama.com/library/` and `www.ollama.com/library/`, without ASCII case
  sensitivity. The first path component after `library/` becomes a `Model`;
  later path components, query text, and fragments are ignored.
- Other slash-separated input uses the generic rule: one component is `Org`,
  while two or more components are `Model` using only the first two.

### CivitAI

- An all-ASCII-digit value such as `827184` becomes
  `ModelTarget::Model("827184")`.
- ModelForge recognizes HTTP and HTTPS URLs under `civitai.com/models/` and
  `www.civitai.com/models/`, without ASCII case sensitivity. If the first
  component after `models/` is nonempty and all ASCII digits, that component
  becomes a `Model`; a following path, query, or fragment is ignored.
- A single nonnumeric value such as `realistic-vision` follows the generic rule
  and becomes `Org`, where `Org` represents a search keyword for this use.
- Other slash-separated input follows the same generic one-component/two-
  component classification described above.

## Normalized Results

`ModelTarget` is the complete normalized result type:

- `Org(String)` represents a namespace, organization, owner, or search keyword.
- `Model(String)` represents a concrete model identifier as classified by the
  parser.
- `Top` and `Latest` represent provider-specific discovery presets.

Use `is_org`, `is_model`, or `is_preset` for classification. `org` and `model`
borrow only their matching variant's string. `value` borrows the string from
either `Org` or `Model` and returns `None` for presets. `kind` returns `org`,
`model`, `top`, or `latest`.

These variants express parsed intent, not proof that a provider resource
exists. In particular, an `Org` may be a search term and a `Model` has not been
checked against a registry.

## Credentials And Environment

ModelForge has no credential API, reads no environment variables, stores no
tokens, and performs no authentication. It has no required environment
variables. A consuming application owns provider-specific credential names,
secret loading, authentication, and redaction.

Do not infer permission to use a credential from a successfully parsed target.
Do not place tokens in target strings, errors, logs, or selection displays.
Pass credentials only to the selected provider client after applying the
consumer's authorization policy.

## Discovery, Selection, And Download Boundaries

ModelForge performs no network or filesystem I/O. It does not:

- list, search, or validate provider resources;
- resolve `Org`, `Top`, or `Latest` to candidates;
- inspect model versions, variants, files, sizes, licenses, or metadata;
- select a candidate, version, file, or output path;
- clone, pull, download, cache, unpack, or write models.

The consumer must map a normalized target to its own provider client. A
consumer may treat `Org` as a namespace or search request, `Model` as a request
to look up one model, and presets as provider-specific discovery requests, but
the exact query is outside this crate. Candidate filtering and user selection
must occur after discovery. Downloading must occur only after selection and any
required authorization, license, size, path, and overwrite checks.

## Safe Agent Workflow

1. Obtain the provider independently from the target. Parse it with
   `ModelProvider::from_slug`; stop on `None` instead of guessing.
2. Parse the target with `parse_model_target_for_provider` and handle its
   `Result` without substituting another provider on error.
3. Inspect the returned variant. Treat `Org`, `Top`, and `Latest` as unresolved
   discovery intents. Treat `Model` as an unverified identifier.
4. Apply stricter provider-client validation before any request. This is
   important because ModelForge deliberately accepts broad path-like input.
5. Use only the chosen provider's client and credential policy for discovery.
   Keep discovery read-only and surface ambiguity rather than selecting an
   arbitrary result.
6. Before a download, make the exact model, version or variant, files, expected
   size, license constraints, destination, and overwrite behavior explicit in
   the consuming application.
7. Keep download and filesystem failures separate from `ModelForgeError`;
   ModelForge cannot produce or interpret those failures.

## Limits And Errors

The public `Result<T>` alias uses `ModelForgeError`. Its only current variant is
`ModelForgeError::InvalidTarget(String)`. `target()` borrows that payload, and
its display form is `invalid model target '<payload>'`.

An empty or whitespace-only input produces `InvalidTarget("empty model
target")`. A recognized Hugging Face URL with no usable path also produces an
invalid target. Beyond those cases parsing is intentionally permissive:

- It does not validate provider resource existence or identifier grammar.
- It does not generally validate URL syntax or reject a URL for the wrong
  provider.
- Unrecognized or malformed provider URLs can fall through to generic slash
  parsing and still produce an `Org` or `Model`.
- Generic slash parsing removes empty components, uses at most the first two,
  and does not generally strip query strings or fragments. Recognized provider
  URL paths have the special cleanup described above.
- CivitAI model-page recognition requires a numeric first component. A
  nonnumeric or empty model-page suffix falls through to generic parsing.
- An empty Ollama library suffix falls through to generic parsing.
- The API does not implement `FromStr`, serialization, provider clients, async
  operations, retries, rate limiting, or cancellation.

Agents should therefore regard successful parsing as normalization only and
leave strict validation to the provider integration.

## Public API Examples

Resolve a provider and parse a concrete target:

```rust
use modelforge::{ModelProvider, ModelTarget, parse_model_target_for_provider};

let provider = ModelProvider::from_slug("hugging-face").expect("known provider");
let target = parse_model_target_for_provider(
    "https://huggingface.co/openai-community/gpt2?library=transformers#files",
    provider,
)?;

assert_eq!(provider, ModelProvider::Hf);
assert_eq!(target, ModelTarget::Model("openai-community/gpt2".to_string()));
assert_eq!(target.kind(), "model");
assert_eq!(target.value(), Some("openai-community/gpt2"));
# Ok::<(), modelforge::ModelForgeError>(())
```

Branch at the integration boundary without performing discovery in ModelForge:

```rust
use modelforge::{ModelProvider, ModelTarget, parse_model_target_for_provider};

let target = parse_model_target_for_provider("newest", ModelProvider::Civitai)?;

match target {
    ModelTarget::Org(query) => assert!(!query.is_empty()),
    ModelTarget::Model(id) => assert!(!id.is_empty()),
    ModelTarget::Top => unreachable!(),
    ModelTarget::Latest => {
        // The consuming CivitAI client may now perform its latest-model query.
    }
}
# Ok::<(), modelforge::ModelForgeError>(())
```

Inspect an error payload:

```rust
let error = modelforge::parse_model_target(" ").unwrap_err();
assert_eq!(error.target(), "empty model target");
assert_eq!(error.to_string(), "invalid model target 'empty model target'");
```