Skip to content

@lastshotlabs/slingshot-ai

npm install @lastshotlabs/slingshot-ai

Source: packages/slingshot-ai/src/plugin.ts

Source: packages/slingshot-ai/src/config.ts

FieldDescription
`z.unknown()).optional(),
timeoutMs`Optional durable request-scoped reservation controller; invoked for every provider attempt.’, ), requireScope: z .boolean() .default(false) .describe(‘Reject generation without spendScope when a spend controller is configured.

Source: packages/slingshot-ai/src/config.ts

Capability descriptors: defaults, merging, and consistency checks.

function assertCapabilitiesConsistent(caps: ProviderCapabilities, who: string): void

Source: packages/slingshot-ai/src/provider/capabilities.ts

Provider registry.

Shape copied deliberately from slingshot-oauth/src/connections.ts: a built-in registry keyed by kind (which defaults to the config key), plus a createProvider escape hatch, plus an “unknown kind” error that lists what IS available. Same problem, same solved shape.

async function buildProvider(name: string, config: AiProviderConfig, deps: BuildProviderDeps,): Promise<AiProvider>

Source: packages/slingshot-ai/src/provider/registry.ts

Provider registry.

Shape copied deliberately from slingshot-oauth/src/connections.ts: a built-in registry keyed by kind (which defaults to the config key), plus a createProvider escape hatch, plus an “unknown kind” error that lists what IS available. Same problem, same solved shape.

function builtinProviderKinds(): readonly string[]

Source: packages/slingshot-ai/src/provider/registry.ts

Structured output: zod → JSON Schema → a validated object, on ANY provider.

This is the riskiest code in the package, and the difference between “works on Ollama” and “doesn’t”. Three paths, chosen from the provider’s declared capabilities:

native → the provider enforces the schema. We still re-validate. json-mode → syntactically valid JSON, unenforced shape. We validate. prompt → no guarantee at all. We instruct, extract, validate, and repair.

Note what is common to all three: we always validate. The provider’s own parsed object is advisory. That is why Anthropic returning parsed_output: null and a 7B model emitting a fenced code block with a trailing comma converge on exactly one code path.

function chooseStructuredMode(capabilities: ProviderCapabilities): StructuredMode

Source: packages/slingshot-ai/src/lib/structured.ts

createAiPackage() — the package factory.

function createAiPackage(rawConfig: AiPackageConfigInput): SlingshotPackageDefinition

Source: packages/slingshot-ai/src/plugin.ts

async function createAnthropicProvider(name: string, config: AiProviderConfig, deps: BuildProviderDeps,): Promise<AiProvider>

Source: packages/slingshot-ai/src/provider/anthropic.ts

function createGeminiProvider(name: string, config: AiProviderConfig, deps: BuildProviderDeps,): AiProvider

Source: packages/slingshot-ai/src/provider/gemini.ts

The OpenAI-compatible adapter — plain fetch, ZERO dependencies.

This is the highest-leverage adapter in the package, because the /chat/completions shape is the de-facto lingua franca: Ollama, LM Studio, llama.cpp, vLLM, OpenRouter, Groq, Together, and Gemini’s compat endpoint all speak it. One adapter therefore turns “free local inference on the home server” into a config change.

Which is also the design problem. Those backends have wildly different real abilities: vLLM with a grammar backend genuinely enforces a JSON Schema; a small Ollama model cannot reliably close a brace. So capabilities here are config-declarable, and the built-in defaults are deliberately pessimistic — an under-declared provider costs you a JSON repair loop, while an over-declared one costs you a card that never validates and a party that stops. Only the wrong one of those is silent.

function createOpenAiCompatibleProvider(name: string, config: AiProviderConfig, deps: BuildProviderDeps,): AiProvider

Source: packages/slingshot-ai/src/provider/openaiCompatible.ts

The OpenAI-compatible adapter — plain fetch, ZERO dependencies.

This is the highest-leverage adapter in the package, because the /chat/completions shape is the de-facto lingua franca: Ollama, LM Studio, llama.cpp, vLLM, OpenRouter, Groq, Together, and Gemini’s compat endpoint all speak it. One adapter therefore turns “free local inference on the home server” into a config change.

Which is also the design problem. Those backends have wildly different real abilities: vLLM with a grammar backend genuinely enforces a JSON Schema; a small Ollama model cannot reliably close a brace. So capabilities here are config-declarable, and the built-in defaults are deliberately pessimistic — an under-declared provider costs you a JSON repair loop, while an over-declared one costs you a card that never validates and a party that stops. Only the wrong one of those is silent.

function createOpenAiProvider(name: string, config: AiProviderConfig, deps: BuildProviderDeps,): AiProvider

Source: packages/slingshot-ai/src/provider/openaiCompatible.ts

Structured output: zod → JSON Schema → a validated object, on ANY provider.

This is the riskiest code in the package, and the difference between “works on Ollama” and “doesn’t”. Three paths, chosen from the provider’s declared capabilities:

native → the provider enforces the schema. We still re-validate. json-mode → syntactically valid JSON, unenforced shape. We validate. prompt → no guarantee at all. We instruct, extract, validate, and repair.

Note what is common to all three: we always validate. The provider’s own parsed object is advisory. That is why Anthropic returning parsed_output: null and a 7B model emitting a fenced code block with a trailing comma converge on exactly one code path.

function extractJson(text: string): string | null

Source: packages/slingshot-ai/src/lib/structured.ts

Structured output: zod → JSON Schema → a validated object, on ANY provider.

This is the riskiest code in the package, and the difference between “works on Ollama” and “doesn’t”. Three paths, chosen from the provider’s declared capabilities:

native → the provider enforces the schema. We still re-validate. json-mode → syntactically valid JSON, unenforced shape. We validate. prompt → no guarantee at all. We instruct, extract, validate, and repair.

Note what is common to all three: we always validate. The provider’s own parsed object is advisory. That is why Anthropic returning parsed_output: null and a 7B model emitting a fenced code block with a trailing comma converge on exactly one code path.

function parseStructured<T>(options: { schema: z.ZodType<T>; advisory?: unknown; text: string; }): ParseAttempt<T>

Source: packages/slingshot-ai/src/lib/structured.ts

Provider registry.

Shape copied deliberately from slingshot-oauth/src/connections.ts: a built-in registry keyed by kind (which defaults to the config key), plus a createProvider escape hatch, plus an “unknown kind” error that lists what IS available. Same problem, same solved shape.

function registerBuiltinProvider(kind: string, factory: ProviderFactory): void

Source: packages/slingshot-ai/src/provider/registry.ts

Capability descriptors: defaults, merging, and consistency checks.

function resolveCapabilities(base: ProviderCapabilities, overrides?: Partial<ProviderCapabilities>,): ProviderCapabilities

Source: packages/slingshot-ai/src/provider/capabilities.ts

Structured output: zod → JSON Schema → a validated object, on ANY provider.

This is the riskiest code in the package, and the difference between “works on Ollama” and “doesn’t”. Three paths, chosen from the provider’s declared capabilities:

native → the provider enforces the schema. We still re-validate. json-mode → syntactically valid JSON, unenforced shape. We validate. prompt → no guarantee at all. We instruct, extract, validate, and repair.

Note what is common to all three: we always validate. The provider’s own parsed object is advisory. That is why Anthropic returning parsed_output: null and a 7B model emitting a fenced code block with a trailing comma converge on exactly one code path.

function sanitizeJsonSchema(input: unknown): SanitizeResult

Source: packages/slingshot-ai/src/lib/structured.ts

Structured output: zod → JSON Schema → a validated object, on ANY provider.

This is the riskiest code in the package, and the difference between “works on Ollama” and “doesn’t”. Three paths, chosen from the provider’s declared capabilities:

native → the provider enforces the schema. We still re-validate. json-mode → syntactically valid JSON, unenforced shape. We validate. prompt → no guarantee at all. We instruct, extract, validate, and repair.

Note what is common to all three: we always validate. The provider’s own parsed object is advisory. That is why Anthropic returning parsed_output: null and a 7B model emitting a fenced code block with a trailing comma converge on exactly one code path.

function toJsonSchema(schema: z.ZodType<unknown>, options: { logger?: AiLogger; strict: boolean; name: string },): Record<string, unknown>

Source: packages/slingshot-ai/src/lib/structured.ts

Public contract for slingshot-ai.

Three capabilities, not one — and not for symmetry:

  • AiClientCap is generation. Nine methods, all about producing tokens.
  • AiModerationCap is safety verdicts. Independently useful: moderating player-typed content involves no generation, and a package that only needs safety shouldn’t depend on a surface that can spend money. It is also the swap point for a non-LLM classifier.
  • AiUsageCap is a read-only projection over usage + spend. Different lifecycle, different consumer (admin/observability, never generation code).

The split makes capabilities.requires honest: a consumer that declares [AiClientCap, AiModerationCap] gets a boot-time error if the package isn’t installed, and says exactly what it uses.

Source: packages/slingshot-ai/src/public.ts

Public contract for slingshot-ai.

Three capabilities, not one — and not for symmetry:

  • AiClientCap is generation. Nine methods, all about producing tokens.
  • AiModerationCap is safety verdicts. Independently useful: moderating player-typed content involves no generation, and a package that only needs safety shouldn’t depend on a surface that can spend money. It is also the swap point for a non-LLM classifier.
  • AiUsageCap is a read-only projection over usage + spend. Different lifecycle, different consumer (admin/observability, never generation code).

The split makes capabilities.requires honest: a consumer that declares [AiClientCap, AiModerationCap] gets a boot-time error if the package isn’t installed, and says exactly what it uses.

Source: packages/slingshot-ai/src/public.ts

Public contract for slingshot-ai.

Three capabilities, not one — and not for symmetry:

  • AiClientCap is generation. Nine methods, all about producing tokens.
  • AiModerationCap is safety verdicts. Independently useful: moderating player-typed content involves no generation, and a package that only needs safety shouldn’t depend on a surface that can spend money. It is also the swap point for a non-LLM classifier.
  • AiUsageCap is a read-only projection over usage + spend. Different lifecycle, different consumer (admin/observability, never generation code).

The split makes capabilities.requires honest: a consumer that declares [AiClientCap, AiModerationCap] gets a boot-time error if the package isn’t installed, and says exactly what it uses.

Source: packages/slingshot-ai/src/public.ts

Public contract for slingshot-ai.

Three capabilities, not one — and not for symmetry:

  • AiClientCap is generation. Nine methods, all about producing tokens.
  • AiModerationCap is safety verdicts. Independently useful: moderating player-typed content involves no generation, and a package that only needs safety shouldn’t depend on a surface that can spend money. It is also the swap point for a non-LLM classifier.
  • AiUsageCap is a read-only projection over usage + spend. Different lifecycle, different consumer (admin/observability, never generation code).

The split makes capabilities.requires honest: a consumer that declares [AiClientCap, AiModerationCap] gets a boot-time error if the package isn’t installed, and says exactly what it uses.

Source: packages/slingshot-ai/src/public.ts

Source: packages/slingshot-ai/src/entities/aiUsage.ts

Capability descriptors: defaults, merging, and consistency checks.

Source: packages/slingshot-ai/src/provider/capabilities.ts

Cost computation.

The load-bearing distinction in this file is null vs 0:

  • a NUMBER → we know the price and this is the cost
  • 0 → genuinely free (local inference)
  • null → we do NOT know the price

null must survive aggregation (as unpricedCalls) rather than being summed as zero. A dashboard that quietly reports $0.00 because it couldn’t price half the calls is worse than one that says “I don’t know”.

Source: packages/slingshot-ai/src/lib/pricing.ts

Error taxonomy for slingshot-ai.

Consumers catch THESE classes — never a provider SDK’s own error types. Each adapter maps its SDK errors into this taxonomy (most-specific-first, never by string-matching), which is what lets an app swap Anthropic for a local model without touching a single catch block.

The throw-vs-return rule:

  • THROW when the call produced no usable result through no choice of the caller (bad config, spend limit, rate limit after retries, connection failure, unparseable structured output, refusal, timeout).
  • RETURN when the system worked and made a decision the caller may want to inspect (moderation verdicts, degradations, usage, stopReason: 'max_tokens' — the caller decides whether truncation matters).

Source: packages/slingshot-ai/src/errors.ts

Error taxonomy for slingshot-ai.

Consumers catch THESE classes — never a provider SDK’s own error types. Each adapter maps its SDK errors into this taxonomy (most-specific-first, never by string-matching), which is what lets an app swap Anthropic for a local model without touching a single catch block.

The throw-vs-return rule:

  • THROW when the call produced no usable result through no choice of the caller (bad config, spend limit, rate limit after retries, connection failure, unparseable structured output, refusal, timeout).
  • RETURN when the system worked and made a decision the caller may want to inspect (moderation verdicts, degradations, usage, stopReason: 'max_tokens' — the caller decides whether truncation matters).

Source: packages/slingshot-ai/src/errors.ts

Error taxonomy for slingshot-ai.

Consumers catch THESE classes — never a provider SDK’s own error types. Each adapter maps its SDK errors into this taxonomy (most-specific-first, never by string-matching), which is what lets an app swap Anthropic for a local model without touching a single catch block.

The throw-vs-return rule:

  • THROW when the call produced no usable result through no choice of the caller (bad config, spend limit, rate limit after retries, connection failure, unparseable structured output, refusal, timeout).
  • RETURN when the system worked and made a decision the caller may want to inspect (moderation verdicts, degradations, usage, stopReason: 'max_tokens' — the caller decides whether truncation matters).

Source: packages/slingshot-ai/src/errors.ts

Error taxonomy for slingshot-ai.

Consumers catch THESE classes — never a provider SDK’s own error types. Each adapter maps its SDK errors into this taxonomy (most-specific-first, never by string-matching), which is what lets an app swap Anthropic for a local model without touching a single catch block.

The throw-vs-return rule:

  • THROW when the call produced no usable result through no choice of the caller (bad config, spend limit, rate limit after retries, connection failure, unparseable structured output, refusal, timeout).
  • RETURN when the system worked and made a decision the caller may want to inspect (moderation verdicts, degradations, usage, stopReason: 'max_tokens' — the caller decides whether truncation matters).

Source: packages/slingshot-ai/src/errors.ts

Error taxonomy for slingshot-ai.

Consumers catch THESE classes — never a provider SDK’s own error types. Each adapter maps its SDK errors into this taxonomy (most-specific-first, never by string-matching), which is what lets an app swap Anthropic for a local model without touching a single catch block.

The throw-vs-return rule:

  • THROW when the call produced no usable result through no choice of the caller (bad config, spend limit, rate limit after retries, connection failure, unparseable structured output, refusal, timeout).
  • RETURN when the system worked and made a decision the caller may want to inspect (moderation verdicts, degradations, usage, stopReason: 'max_tokens' — the caller decides whether truncation matters).

Source: packages/slingshot-ai/src/errors.ts

Error taxonomy for slingshot-ai.

Consumers catch THESE classes — never a provider SDK’s own error types. Each adapter maps its SDK errors into this taxonomy (most-specific-first, never by string-matching), which is what lets an app swap Anthropic for a local model without touching a single catch block.

The throw-vs-return rule:

  • THROW when the call produced no usable result through no choice of the caller (bad config, spend limit, rate limit after retries, connection failure, unparseable structured output, refusal, timeout).
  • RETURN when the system worked and made a decision the caller may want to inspect (moderation verdicts, degradations, usage, stopReason: 'max_tokens' — the caller decides whether truncation matters).

Source: packages/slingshot-ai/src/errors.ts

Error taxonomy for slingshot-ai.

Consumers catch THESE classes — never a provider SDK’s own error types. Each adapter maps its SDK errors into this taxonomy (most-specific-first, never by string-matching), which is what lets an app swap Anthropic for a local model without touching a single catch block.

The throw-vs-return rule:

  • THROW when the call produced no usable result through no choice of the caller (bad config, spend limit, rate limit after retries, connection failure, unparseable structured output, refusal, timeout).
  • RETURN when the system worked and made a decision the caller may want to inspect (moderation verdicts, degradations, usage, stopReason: 'max_tokens' — the caller decides whether truncation matters).

Source: packages/slingshot-ai/src/errors.ts

Error taxonomy for slingshot-ai.

Consumers catch THESE classes — never a provider SDK’s own error types. Each adapter maps its SDK errors into this taxonomy (most-specific-first, never by string-matching), which is what lets an app swap Anthropic for a local model without touching a single catch block.

The throw-vs-return rule:

  • THROW when the call produced no usable result through no choice of the caller (bad config, spend limit, rate limit after retries, connection failure, unparseable structured output, refusal, timeout).
  • RETURN when the system worked and made a decision the caller may want to inspect (moderation verdicts, degradations, usage, stopReason: 'max_tokens' — the caller decides whether truncation matters).

Source: packages/slingshot-ai/src/errors.ts

Error taxonomy for slingshot-ai.

Consumers catch THESE classes — never a provider SDK’s own error types. Each adapter maps its SDK errors into this taxonomy (most-specific-first, never by string-matching), which is what lets an app swap Anthropic for a local model without touching a single catch block.

The throw-vs-return rule:

  • THROW when the call produced no usable result through no choice of the caller (bad config, spend limit, rate limit after retries, connection failure, unparseable structured output, refusal, timeout).
  • RETURN when the system worked and made a decision the caller may want to inspect (moderation verdicts, degradations, usage, stopReason: 'max_tokens' — the caller decides whether truncation matters).

Source: packages/slingshot-ai/src/errors.ts

Error taxonomy for slingshot-ai.

Consumers catch THESE classes — never a provider SDK’s own error types. Each adapter maps its SDK errors into this taxonomy (most-specific-first, never by string-matching), which is what lets an app swap Anthropic for a local model without touching a single catch block.

The throw-vs-return rule:

  • THROW when the call produced no usable result through no choice of the caller (bad config, spend limit, rate limit after retries, connection failure, unparseable structured output, refusal, timeout).
  • RETURN when the system worked and made a decision the caller may want to inspect (moderation verdicts, degradations, usage, stopReason: 'max_tokens' — the caller decides whether truncation matters).

Source: packages/slingshot-ai/src/errors.ts

The seams between the orchestrator and the framework.

Every one of these is STRUCTURAL — the real framework types satisfy them without being imported here. Two reasons that matters:

  1. The orchestrator stays unit-testable with three-line fakes, so the tests that assert the interesting behavior (fail-closed moderation, pre-flight spend, cache coalescing) don’t have to boot an app.
  2. Every one of them is OPTIONAL. An app with no cache adapter, no queue, and no database still gets a fully working AI client — it just gets the in-memory versions, and says so.

Source: packages/slingshot-ai/src/lib/seams.ts

The neutral surface: what apps consume.

Nothing here names a provider. An app written against these types runs on Claude, on GPT, or on a local Llama with a config change and no code change.

Source: packages/slingshot-ai/src/types.ts

The neutral surface: what apps consume.

Nothing here names a provider. An app written against these types runs on Claude, on GPT, or on a local Llama with a config change and no code change.

Source: packages/slingshot-ai/src/types.ts

The seams between the orchestrator and the framework.

Every one of these is STRUCTURAL — the real framework types satisfy them without being imported here. Two reasons that matters:

  1. The orchestrator stays unit-testable with three-line fakes, so the tests that assert the interesting behavior (fail-closed moderation, pre-flight spend, cache coalescing) don’t have to boot an app.
  2. Every one of them is OPTIONAL. An app with no cache adapter, no queue, and no database still gets a fully working AI client — it just gets the in-memory versions, and says so.

Source: packages/slingshot-ai/src/lib/seams.ts

The neutral surface: what apps consume.

Nothing here names a provider. An app written against these types runs on Claude, on GPT, or on a local Llama with a config change and no code change.

Source: packages/slingshot-ai/src/types.ts

The provider seam.

THE RULE THAT MAKES THIS PACKAGE WORK: providers are dumb transports. A provider translates one NormalizedRequest into one HTTP call and normalizes the response back into a ProviderResult. That is all it does.

Providers do NOT:

  • validate schemas (the orchestrator safeParses every result, from every provider — see ProviderResult.structured below)
  • compute cost (they report raw token counts; pricing is orchestrator policy)
  • decide retries (they classify errors; the retry layer decides)
  • moderate (that is an independent call, possibly to a different provider)

Keeping policy out of adapters is why adding a new provider later is mechanical: write the translation, declare your capabilities, pass the conformance suite.

Source: packages/slingshot-ai/src/provider/types.ts

The neutral surface: what apps consume.

Nothing here names a provider. An app written against these types runs on Claude, on GPT, or on a local Llama with a config change and no code change.

Source: packages/slingshot-ai/src/types.ts

The neutral surface: what apps consume.

Nothing here names a provider. An app written against these types runs on Claude, on GPT, or on a local Llama with a config change and no code change.

Source: packages/slingshot-ai/src/types.ts

The provider seam.

THE RULE THAT MAKES THIS PACKAGE WORK: providers are dumb transports. A provider translates one NormalizedRequest into one HTTP call and normalizes the response back into a ProviderResult. That is all it does.

Providers do NOT:

  • validate schemas (the orchestrator safeParses every result, from every provider — see ProviderResult.structured below)
  • compute cost (they report raw token counts; pricing is orchestrator policy)
  • decide retries (they classify errors; the retry layer decides)
  • moderate (that is an independent call, possibly to a different provider)

Keeping policy out of adapters is why adding a new provider later is mechanical: write the translation, declare your capabilities, pass the conformance suite.

Source: packages/slingshot-ai/src/provider/types.ts

Source: packages/slingshot-ai/src/types.ts

Source: packages/slingshot-ai/src/types.ts

Source: packages/slingshot-ai/src/types.ts

The neutral surface: what apps consume.

Nothing here names a provider. An app written against these types runs on Claude, on GPT, or on a local Llama with a config change and no code change.

Source: packages/slingshot-ai/src/types.ts

The neutral surface: what apps consume.

Nothing here names a provider. An app written against these types runs on Claude, on GPT, or on a local Llama with a config change and no code change.

Source: packages/slingshot-ai/src/types.ts

The neutral surface: what apps consume.

Nothing here names a provider. An app written against these types runs on Claude, on GPT, or on a local Llama with a config change and no code change.

Source: packages/slingshot-ai/src/types.ts

The neutral surface: what apps consume.

Nothing here names a provider. An app written against these types runs on Claude, on GPT, or on a local Llama with a config change and no code change.

Source: packages/slingshot-ai/src/types.ts

Source: packages/slingshot-ai/src/types.ts

Source: packages/slingshot-ai/src/types.ts

The neutral surface: what apps consume.

Nothing here names a provider. An app written against these types runs on Claude, on GPT, or on a local Llama with a config change and no code change.

Source: packages/slingshot-ai/src/types.ts

Source: packages/slingshot-ai/src/types.ts

The neutral surface: what apps consume.

Nothing here names a provider. An app written against these types runs on Claude, on GPT, or on a local Llama with a config change and no code change.

Source: packages/slingshot-ai/src/types.ts

Source: packages/slingshot-ai/src/types.ts

The seams between the orchestrator and the framework.

Every one of these is STRUCTURAL — the real framework types satisfy them without being imported here. Two reasons that matters:

  1. The orchestrator stays unit-testable with three-line fakes, so the tests that assert the interesting behavior (fail-closed moderation, pre-flight spend, cache coalescing) don’t have to boot an app.
  2. Every one of them is OPTIONAL. An app with no cache adapter, no queue, and no database still gets a fully working AI client — it just gets the in-memory versions, and says so.

Source: packages/slingshot-ai/src/lib/seams.ts

The seams between the orchestrator and the framework.

Every one of these is STRUCTURAL — the real framework types satisfy them without being imported here. Two reasons that matters:

  1. The orchestrator stays unit-testable with three-line fakes, so the tests that assert the interesting behavior (fail-closed moderation, pre-flight spend, cache coalescing) don’t have to boot an app.
  2. Every one of them is OPTIONAL. An app with no cache adapter, no queue, and no database still gets a fully working AI client — it just gets the in-memory versions, and says so.

Source: packages/slingshot-ai/src/lib/seams.ts

Source: packages/slingshot-ai/src/types.ts

Source: packages/slingshot-ai/src/types.ts

The neutral surface: what apps consume.

Nothing here names a provider. An app written against these types runs on Claude, on GPT, or on a local Llama with a config change and no code change.

Source: packages/slingshot-ai/src/types.ts

The provider seam.

THE RULE THAT MAKES THIS PACKAGE WORK: providers are dumb transports. A provider translates one NormalizedRequest into one HTTP call and normalizes the response back into a ProviderResult. That is all it does.

Providers do NOT:

  • validate schemas (the orchestrator safeParses every result, from every provider — see ProviderResult.structured below)
  • compute cost (they report raw token counts; pricing is orchestrator policy)
  • decide retries (they classify errors; the retry layer decides)
  • moderate (that is an independent call, possibly to a different provider)

Keeping policy out of adapters is why adding a new provider later is mechanical: write the translation, declare your capabilities, pass the conformance suite.

Source: packages/slingshot-ai/src/provider/types.ts

The provider seam.

THE RULE THAT MAKES THIS PACKAGE WORK: providers are dumb transports. A provider translates one NormalizedRequest into one HTTP call and normalizes the response back into a ProviderResult. That is all it does.

Providers do NOT:

  • validate schemas (the orchestrator safeParses every result, from every provider — see ProviderResult.structured below)
  • compute cost (they report raw token counts; pricing is orchestrator policy)
  • decide retries (they classify errors; the retry layer decides)
  • moderate (that is an independent call, possibly to a different provider)

Keeping policy out of adapters is why adding a new provider later is mechanical: write the translation, declare your capabilities, pass the conformance suite.

Source: packages/slingshot-ai/src/provider/types.ts

The provider seam.

THE RULE THAT MAKES THIS PACKAGE WORK: providers are dumb transports. A provider translates one NormalizedRequest into one HTTP call and normalizes the response back into a ProviderResult. That is all it does.

Providers do NOT:

  • validate schemas (the orchestrator safeParses every result, from every provider — see ProviderResult.structured below)
  • compute cost (they report raw token counts; pricing is orchestrator policy)
  • decide retries (they classify errors; the retry layer decides)
  • moderate (that is an independent call, possibly to a different provider)

Keeping policy out of adapters is why adding a new provider later is mechanical: write the translation, declare your capabilities, pass the conformance suite.

Source: packages/slingshot-ai/src/provider/types.ts

The provider seam.

THE RULE THAT MAKES THIS PACKAGE WORK: providers are dumb transports. A provider translates one NormalizedRequest into one HTTP call and normalizes the response back into a ProviderResult. That is all it does.

Providers do NOT:

  • validate schemas (the orchestrator safeParses every result, from every provider — see ProviderResult.structured below)
  • compute cost (they report raw token counts; pricing is orchestrator policy)
  • decide retries (they classify errors; the retry layer decides)
  • moderate (that is an independent call, possibly to a different provider)

Keeping policy out of adapters is why adding a new provider later is mechanical: write the translation, declare your capabilities, pass the conformance suite.

Source: packages/slingshot-ai/src/provider/types.ts

The provider seam.

THE RULE THAT MAKES THIS PACKAGE WORK: providers are dumb transports. A provider translates one NormalizedRequest into one HTTP call and normalizes the response back into a ProviderResult. That is all it does.

Providers do NOT:

  • validate schemas (the orchestrator safeParses every result, from every provider — see ProviderResult.structured below)
  • compute cost (they report raw token counts; pricing is orchestrator policy)
  • decide retries (they classify errors; the retry layer decides)
  • moderate (that is an independent call, possibly to a different provider)

Keeping policy out of adapters is why adding a new provider later is mechanical: write the translation, declare your capabilities, pass the conformance suite.

Source: packages/slingshot-ai/src/provider/types.ts

The provider seam.

THE RULE THAT MAKES THIS PACKAGE WORK: providers are dumb transports. A provider translates one NormalizedRequest into one HTTP call and normalizes the response back into a ProviderResult. That is all it does.

Providers do NOT:

  • validate schemas (the orchestrator safeParses every result, from every provider — see ProviderResult.structured below)
  • compute cost (they report raw token counts; pricing is orchestrator policy)
  • decide retries (they classify errors; the retry layer decides)
  • moderate (that is an independent call, possibly to a different provider)

Keeping policy out of adapters is why adding a new provider later is mechanical: write the translation, declare your capabilities, pass the conformance suite.

Source: packages/slingshot-ai/src/provider/types.ts

The provider seam.

THE RULE THAT MAKES THIS PACKAGE WORK: providers are dumb transports. A provider translates one NormalizedRequest into one HTTP call and normalizes the response back into a ProviderResult. That is all it does.

Providers do NOT:

  • validate schemas (the orchestrator safeParses every result, from every provider — see ProviderResult.structured below)
  • compute cost (they report raw token counts; pricing is orchestrator policy)
  • decide retries (they classify errors; the retry layer decides)
  • moderate (that is an independent call, possibly to a different provider)

Keeping policy out of adapters is why adding a new provider later is mechanical: write the translation, declare your capabilities, pass the conformance suite.

Source: packages/slingshot-ai/src/provider/types.ts

The provider seam.

THE RULE THAT MAKES THIS PACKAGE WORK: providers are dumb transports. A provider translates one NormalizedRequest into one HTTP call and normalizes the response back into a ProviderResult. That is all it does.

Providers do NOT:

  • validate schemas (the orchestrator safeParses every result, from every provider — see ProviderResult.structured below)
  • compute cost (they report raw token counts; pricing is orchestrator policy)
  • decide retries (they classify errors; the retry layer decides)
  • moderate (that is an independent call, possibly to a different provider)

Keeping policy out of adapters is why adding a new provider later is mechanical: write the translation, declare your capabilities, pass the conformance suite.

Source: packages/slingshot-ai/src/provider/types.ts

Source: packages/slingshot-ai/src/types.ts

The neutral surface: what apps consume.

Nothing here names a provider. An app written against these types runs on Claude, on GPT, or on a local Llama with a config change and no code change.

Source: packages/slingshot-ai/src/types.ts

The neutral surface: what apps consume.

Nothing here names a provider. An app written against these types runs on Claude, on GPT, or on a local Llama with a config change and no code change.

Source: packages/slingshot-ai/src/types.ts

Source: packages/slingshot-ai/src/types.ts

The neutral surface: what apps consume.

Nothing here names a provider. An app written against these types runs on Claude, on GPT, or on a local Llama with a config change and no code change.

Source: packages/slingshot-ai/src/types.ts

Source: packages/slingshot-ai/src/types.ts

Error taxonomy for slingshot-ai.

Consumers catch THESE classes — never a provider SDK’s own error types. Each adapter maps its SDK errors into this taxonomy (most-specific-first, never by string-matching), which is what lets an app swap Anthropic for a local model without touching a single catch block.

The throw-vs-return rule:

  • THROW when the call produced no usable result through no choice of the caller (bad config, spend limit, rate limit after retries, connection failure, unparseable structured output, refusal, timeout).
  • RETURN when the system worked and made a decision the caller may want to inspect (moderation verdicts, degradations, usage, stopReason: 'max_tokens' — the caller decides whether truncation matters).

Source: packages/slingshot-ai/src/errors.ts

Source: packages/slingshot-ai/src/types.ts

Source: packages/slingshot-ai/src/types.ts

Source: packages/slingshot-ai/src/types.ts

Source: packages/slingshot-ai/src/config.ts

Source: packages/slingshot-ai/src/config.ts

Configuration for slingshot-ai.

Note the deliberate absence of a top-level .superRefine(): validatePluginConfig is typed <S extends z.ZodObject> and warnUnknownPluginKeys introspects the object shape, so a refinement wrapper would break both. Cross-field checks live in createAiPackage() as plain imperative throws — which also produces better error messages.

Source: packages/slingshot-ai/src/config.ts

Configuration for slingshot-ai.

Note the deliberate absence of a top-level .superRefine(): validatePluginConfig is typed <S extends z.ZodObject> and warnUnknownPluginKeys introspects the object shape, so a refinement wrapper would break both. Cross-field checks live in createAiPackage() as plain imperative throws — which also produces better error messages.

Source: packages/slingshot-ai/src/config.ts

Source: packages/slingshot-ai/src/config.ts

Source: packages/slingshot-ai/src/types.ts

Source: packages/slingshot-ai/src/types.ts

Source: packages/slingshot-ai/src/types.ts

The neutral surface: what apps consume.

Nothing here names a provider. An app written against these types runs on Claude, on GPT, or on a local Llama with a config change and no code change.

Source: packages/slingshot-ai/src/types.ts

Source: packages/slingshot-ai/src/types.ts

Source: packages/slingshot-ai/src/types.ts

Configuration for slingshot-ai.

Note the deliberate absence of a top-level .superRefine(): validatePluginConfig is typed <S extends z.ZodObject> and warnUnknownPluginKeys introspects the object shape, so a refinement wrapper would break both. Cross-field checks live in createAiPackage() as plain imperative throws — which also produces better error messages.

Source: packages/slingshot-ai/src/config.ts

The provider seam.

THE RULE THAT MAKES THIS PACKAGE WORK: providers are dumb transports. A provider translates one NormalizedRequest into one HTTP call and normalizes the response back into a ProviderResult. That is all it does.

Providers do NOT:

  • validate schemas (the orchestrator safeParses every result, from every provider — see ProviderResult.structured below)
  • compute cost (they report raw token counts; pricing is orchestrator policy)
  • decide retries (they classify errors; the retry layer decides)
  • moderate (that is an independent call, possibly to a different provider)

Keeping policy out of adapters is why adding a new provider later is mechanical: write the translation, declare your capabilities, pass the conformance suite.

Source: packages/slingshot-ai/src/provider/types.ts

The provider seam.

THE RULE THAT MAKES THIS PACKAGE WORK: providers are dumb transports. A provider translates one NormalizedRequest into one HTTP call and normalizes the response back into a ProviderResult. That is all it does.

Providers do NOT:

  • validate schemas (the orchestrator safeParses every result, from every provider — see ProviderResult.structured below)
  • compute cost (they report raw token counts; pricing is orchestrator policy)
  • decide retries (they classify errors; the retry layer decides)
  • moderate (that is an independent call, possibly to a different provider)

Keeping policy out of adapters is why adding a new provider later is mechanical: write the translation, declare your capabilities, pass the conformance suite.

Source: packages/slingshot-ai/src/provider/types.ts

The neutral surface: what apps consume.

Nothing here names a provider. An app written against these types runs on Claude, on GPT, or on a local Llama with a config change and no code change.

Source: packages/slingshot-ai/src/types.ts