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

Reject internally-incoherent descriptors at construction time.

The important one: promptCaching: 'explicit' without promptCacheMinTokens is a trap, because the orchestrator would have no threshold below which to refuse a breakpoint — and a below-minimum breakpoint is accepted by the API and then silently does nothing.

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

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

Resolve one configured provider into a live AiProvider.

Resolution order, most-specific first:

  1. provider — a ready-made instance (DI; how tests inject the fake).
  2. createProvider — a custom factory (the escape hatch).
  3. kind in the built-in registry (kind defaults to the config key, so providers: { anthropic: {...} } needs no explicit kind).
async function buildProvider(name: string, config: AiProviderConfig, deps: BuildProviderDeps,): Promise<AiProvider>

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

The kinds currently registered. Used in error messages and by tests.

function builtinProviderKinds(): readonly string[]

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

Pick how to get JSON out of this provider.

function chooseStructuredMode(capabilities: ProviderCapabilities): StructuredMode

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

Create the slingshot-ai package.

Two framework contracts shape this file:

  1. Secrets. Each provider’s API key is resolved from the framework secret repository during setupMiddleware — never from process.env — and a provider that declares a key it cannot get fails the BOOT. A party should not discover a missing API key at the moment the first player taps a button.

  2. Capability publication is declarative. The framework resolves the values in capabilities.provides EAGERLY, at publishPackageRuntimeState, which runs before this package’s setupMiddleware — so the real client does not exist yet at resolution time. Publishing imperatively instead doesn’t work either: the framework re-runs its declarative pass at the top of setupPost and wipes the slot. The way through (precedent: slingshot-notifications) is to publish stable FACADES whose methods defer to a ref that setupMiddleware fills in. Consumers get one identity-stable object for the package’s lifetime, and a consumer that somehow reaches a method before boot completes gets a precise error rather than undefined.

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

Any endpoint speaking /chat/completions. baseUrl + defaultModel required.

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

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

OpenAI proper — the same adapter with the endpoint, capabilities, and prices filled in.

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

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

Extract a JSON object from text that may be wrapped in prose, fences, or both.

A model told “reply with JSON” will nonetheless say “Sure! Here’s the JSON:” and wrap it in ```json. This is not a bug in the model; it is the reality of the prompt path, and it is what this function is for.

function extractJson(text: string): string | null

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

The single validation point for EVERY provider.

advisory is whatever parsed object the provider handed us (Anthropic’s parsed_output, OpenAI’s parsed content). We do not trust it — we validate it. If it’s absent or wrong, we fall back to parsing the text.

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

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

Register a built-in adapter. Called by the adapter modules at import time.

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

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

Merge an adapter’s declared capabilities with any config-level overrides.

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

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

Strip keywords a strict structured-output provider rejects, and force additionalProperties: false on every object (which those providers require).

Recursion ($ref pointing at an ancestor) is not supported by strict structured output either; we detect and reject it loudly rather than send something that will 400 with a much worse message.

function sanitizeJsonSchema(input: unknown): SanitizeResult

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

Convert a zod schema to a sanitized JSON Schema.

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

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

Provider-owned package contract for slingshot-ai.

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

Generation: generate / generateStructured / stream / background.

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

Content moderation verdicts. May be backed by a different provider than generation.

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

Usage, cost, and spend reads.

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

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

The conservative baseline.

Every field assumes the LEAST capable plausible backend. An adapter that forgets to declare something therefore gets degraded-but-correct behavior rather than silently-wrong behavior. That asymmetry is on purpose: the cost of under-declaring is a JSON repair loop; the cost of over-declaring is a card that never validates and a party that stops.

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

Built-in price table, per million tokens.

Config providers[x].pricing deep-merges OVER this, so a price change never requires a package release.

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

Invalid configuration: unknown provider kind, missing API key for a configured provider, an undefined moderation policy, a defaultProvider that names no configured provider. Always thrown at boot or at call-construction time — never mid-flight.

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

Moderation blocked the content and the request asked for onBlocked: 'throw'.

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

Base class for every error the package throws.

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

A provider returned an error. Wraps the underlying SDK/HTTP failure.

retryable is the adapter’s judgment, and the retry layer honors it — a 400 is never retried, a connection reset always is.

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

Rate limited. retryAfterMs comes from the provider when it supplies one.

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

The model declined the request (Anthropic’s stop_reason: 'refusal', which arrives as an HTTP 200 with empty or partial content — hence a distinct class rather than a provider error).

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

The pre-flight spend guard refused the call.

Thrown BEFORE the HTTP request — a post-hoc check notices a runaway loop, a pre-flight check stops it.

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

The model’s output could not be coerced into the requested schema, even after the repair loop. Carries the raw text and the final Zod error so the caller can log what actually came back — the single most useful thing to see when a local model misbehaves.

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

The request exceeded timeoutMs.

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

The caller asked for a feature the selected provider does not support, and the package is configured with degradation: 'strict'.

Under the default degradation: 'warn' this is NOT thrown — the orchestrator degrades and records an AiDegradation on the result instead.

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

Satisfied by the framework’s CacheAdapter. Strings only — we serialize.

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

Generation. Consumed via ctx.capabilities.require(AiClientCap).

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

A record that you got less than you asked for.

This is the package’s central honesty mechanism. AiResult.degradations is empty exactly when everything requested was honored — so an app (or a test) can assert result.degradations.length === 0 and mean it.

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

Satisfied by the framework’s SlingshotEventBus (via its dynamic overload).

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

Per-item verdict when an array was moderated in one batched call.

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

The slice of Logger this package needs. Structural, so any Logger satisfies it.

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

Ask the moderator to check this call’s output before it reaches the caller.

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

Safety verdicts. A separate capability because it is independently useful: moderating player-typed content involves no generation at all, and a package that only needs safety should not take a dependency on a surface that can spend money making tokens. It is also the natural swap point for a non-LLM classifier.

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

The transport contract every adapter implements.

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

App-owned durable spend enforcement seam.

Slingshot invokes this for every provider attempt, including retries and structured-output repairs. Throwing from reserve prevents the paid call.

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

Reservation returned by a durable, request-scoped spend controller.

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

One provider attempt presented to an app-supplied durable budget controller.

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

Actual accounting supplied when a reserved provider attempt finishes.

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

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

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

A tool the model may call.

execute lives here — the framework runs the loop — because spend re-entry, the iteration cap and argument validation are the orchestrator’s job, and an app-authored while loop around generate() cannot do any of the three. The app still observes every call through AiStream events and AiResult.toolCalls, which is what a tool-trace UI needs and all it needs.

The framework NEVER retries execute. Retry policy for a side-effecting app function is the app’s business; the package’s retry layer covers the provider call only.

A throwing execute becomes an isError tool result the model can react to. It does not kill the turn and it does not surface as an exception to the app.

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

What one tool call did, recorded on the result.

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

What the orchestrator hands a tool when it runs it. Deliberately minimal.

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

Token counts plus cost. costUsd: null means UNKNOWN; 0 means genuinely free.

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

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

Reads over usage/cost/spend. Consumed by admin surfaces, never by generation code.

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

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

One persisted usage row. Mirrors the AiUsageRecord entity.

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

Persistence for the usage ledger. Satisfied by the entity adapter.

write is deliberately fire-and-forget from the orchestrator’s point of view: a failed usage INSERT must never fail the generation the user is waiting on. It is a ledger, not a transaction.

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

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

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

A system prompt split by stability.

stable MUST be byte-identical across calls — any change invalidates the prompt cache for everything after it. The package hashes each segment and warns, naming the offending segment, when one drifts. Put per-call content (a timestamp, a match id, a roster) in volatile, which is always rendered after the cache breakpoint.

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

Everything a provider needs to make exactly one call.

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

The structured-output ask, pre-resolved by the orchestrator.

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

One tool, in the shape a transport needs.

The orchestrator has already sanitized the JSON Schema (same treatment as NormalizedStructured.jsonSchema), so an adapter sends it as-is.

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

What a provider can actually do.

The orchestrator reads this to decide how to satisfy a request and what to record as a degradation when it cannot. Apps read it via client.capabilitiesOf(provider) to ask “can I rely on X here?”.

An adapter MUST declare these honestly. Over-declaring is the only way to get silently wrong behavior out of this package.

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

What the package hands an adapter factory. The API key is resolved by the PACKAGE.

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

What a provider hands back.

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

A stream of deltas plus the assembled result.

Conformance invariant: the concatenation of every text delta MUST equal (await finalResult()).text. Adapters that emulate streaming over a non-streaming API satisfy this trivially; real streaming adapters get it wrong surprisingly often, which is why the suite asserts it.

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

A tool call as it came off the wire.

argumentsJson is verbatim provider text — NOT parsed, NOT validated, NOT repaired. An adapter that pre-parses this is making a policy decision, which is the one thing a transport must never do.

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

Raw token counts. Cost is NOT the provider’s job.

inputTokens MUST EXCLUDE the cached counts. The four fields are DISJOINT — pricing.computeUsage() bills them additively, so an adapter that reports a total which already contains its cache reads charges for them twice.

This is not hypothetical: Anthropic reports disjoint counts natively (input_tokens excludes cache_read_input_tokens), while the OpenAI family reports prompt_tokens as a TOTAL with cached_tokens as a subset of it. Adapters in that family must subtract. DeepSeek, helpfully, reports the already-disjoint prompt_cache_miss_tokens.

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

A single rendered system block. cache: true marks a breakpoint AFTER this block.

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

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

One addressable chunk of the system prompt. The id is what drift warnings name.

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

Discriminated on purpose: a caller physically cannot ignore whether the work was queued or ran inline. {runId?: string} would let them.

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

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

Every feature the orchestrator can silently do worse. It never does so silently.

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

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

Machine-readable discriminator carried by every error in the taxonomy.

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

Canonical plugin configuration name.

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

Raw input accepted by the AI plugin configuration schema.

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

tool_call_delta and tool_call are two different things and neither is derivable from the other.

  • tool_call_delta is passed through LIVE from the transport, one raw fragment of the model’s argument JSON at a time. It is advisory. Its job is latency: the function NAME lands in the first frame, hundreds of milliseconds before the arguments finish, so a trace UI can render “calling get_lift_trend…” immediately instead of showing nothing. It is emitted for calls that later fail validation too, because a trace is a record of what the model did.
  • tool_call is emitted once per call, AFTER JSON.parse and schema.safeParse, with the validated arguments. This is the one an app may act on. Half of {"lift":"squ is not arguments.

tool_result deliberately carries no return payload: a result can be large, the app already has it (it wrote the tool), and a stream is the wrong place to ship a page of rows.

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

Arbitrary labels recorded on the usage record and used as metric labels.

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/types.ts

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

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

A factory for a custom provider — the escape hatch for anything not built in.

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

A streamed delta.

tool_call_delta is INCREMENTAL and advisory: providers dribble a function’s arguments out over many frames, and argumentsDelta is one raw fragment of that JSON. It exists so a trace UI can render “calling get_lift_trend…” the moment the name lands rather than waiting for the whole call. name is filled in on every event from the adapter’s own by-index accumulator, including the argument-only frames that carry no name on the wire.

It shares its NAME and SHAPE with the public AiStreamEvent variant on purpose, so the orchestrator forwards it by identity. Two events called tool_call with different shapes on either side of this seam would be a trap — the validated public tool_call carries parsed args, and nothing here can produce those.

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

How the orchestrator decided to get JSON out of this provider, given its declared capabilities. The adapter uses this to pick its request idiom.

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

A plain string is treated as fully volatile (never cached).

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