npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@amber-core/sdk-mobile

v0.2.1

Published

Mobile SDK for the Amber platform — typed HTTPS client for LLM proxy, config, streaming, tools, structured output, images.

Readme

@amber-core/sdk-mobile

Typed HTTPS client for the Amber shared platform. Use it from React Native / Expo apps to call the LLM proxy without putting provider keys in the bundle.

v0.2.1 adds llm.images.edit for prompt-driven editing of an existing image. v0.2.0 added tool calling, structured output (llm.object), image input (vision), and image generation (llm.images.generate).

Install

npm install @amber-core/sdk-mobile

Setup

import { createPlatformClient } from "@amber-core/sdk-mobile";

const platform = createPlatformClient({
  apiUrl: "https://abc.execute-api.us-east-1.amazonaws.com",
  llmStreamUrl: "https://xyz.lambda-url.us-east-1.on.aws/",
  appId: process.env.EXPO_PUBLIC_PLATFORM_APP_ID!,
  getToken: () => clerk.getToken({ template: "platform" }),
});

Chat

const reply = await platform.llm.chat({
  model: "gpt-4o-mini",
  messages: [{ role: "user", content: "hi" }],
  estimatedTokens: 100,
});

for await (const chunk of platform.llm.chatStream({
  model: "gpt-4o-mini",
  messages: [{ role: "user", content: "tell me a story" }],
  estimatedTokens: 500,
})) {
  if (chunk.type === "delta") process.stdout.write(chunk.content);
}

Tools (low-level)

const tools = [
  {
    name: "get_weather",
    description: "Get current weather for a city.",
    inputSchema: {
      type: "object",
      properties: { city: { type: "string" } },
      required: ["city"],
    },
  },
];

const r1 = await platform.llm.chat({
  model: "claude-3-5-sonnet-20241022",
  messages: [{ role: "user", content: "Weather in Berlin?" }],
  tools,
  estimatedTokens: 400,
});

if (r1.finishReason === "tool_use") {
  const toolUses = r1.blocks.filter((b) => b.type === "tool_use");
  // Execute tools, then send results back as a new turn:
  const r2 = await platform.llm.chat({
    model: "claude-3-5-sonnet-20241022",
    tools,
    messages: [
      { role: "user", content: "Weather in Berlin?" },
      { role: "assistant", content: r1.blocks },
      {
        role: "user",
        content: toolUses.map((tu) => ({
          type: "tool_result",
          toolUseId: tu.id,
          content: "18°C, cloudy",
        })),
      },
    ],
    estimatedTokens: 500,
  });
  console.log(r2.content);
}

Tool streaming emits tool_use_start → one or more tool_use_delta (raw JSON fragments) → tool_use_stop (with parsed input):

for await (const c of platform.llm.chatStream({ tools, messages, model, estimatedTokens: 500 })) {
  if (c.type === "delta") process.stdout.write(c.content);
  else if (c.type === "tool_use_start") console.log("\n→ calling", c.name);
  else if (c.type === "tool_use_stop") console.log("input:", c.input);
}

Structured output (llm.object)

Pass a JSON Schema and get back a typed object. The backend forces JSON output with both providers (OpenAI native response_format, Anthropic via forced tool — transparent to you).

const { object } = await platform.llm.object<{
  city: string;
  forecast: { day: string; tempC: number }[];
}>({
  model: "gpt-4o-mini",
  messages: [{ role: "user", content: "5-day forecast for Berlin in JSON." }],
  schemaName: "Forecast",
  schema: {
    type: "object",
    properties: {
      city: { type: "string" },
      forecast: {
        type: "array",
        items: {
          type: "object",
          properties: { day: { type: "string" }, tempC: { type: "number" } },
          required: ["day", "tempC"],
        },
      },
    },
    required: ["city", "forecast"],
  },
  estimatedTokens: 600,
});

With Zod (optional)

The /zod subpath converts a Zod 4 schema into JSON Schema. Zod is a peer dep — only loaded if you import this subpath.

import { z } from "zod";
import { fromZod } from "@amber-core/sdk-mobile/zod";

const Forecast = z.object({
  city: z.string(),
  forecast: z.array(z.object({ day: z.string(), tempC: z.number() })),
});

const { object } = await platform.llm.object<z.infer<typeof Forecast>>({
  model: "gpt-4o-mini",
  messages: [{ role: "user", content: "5-day forecast for Berlin." }],
  schema: fromZod(Forecast),
  schemaName: "Forecast",
  estimatedTokens: 600,
});

Vision (image input)

Send images as content blocks. Both URL and base64 sources work with OpenAI; Anthropic accepts base64 only.

import { estimateImageTokens } from "@amber-core/sdk-mobile";

const image = {
  type: "image" as const,
  source: { type: "url" as const, url: "https://example.com/cat.jpg" },
};

await platform.llm.chat({
  model: "gpt-4o",
  messages: [
    {
      role: "user",
      content: [
        image,
        { type: "text", text: "What breed?" },
      ],
    },
  ],
  estimatedTokens: 400 + estimateImageTokens(image, "openai", { width: 1024, height: 768 }),
});

Image generation

const res = await platform.llm.images.generate({
  model: "dall-e-3",
  prompt: "An isometric studio for a small mobile team",
  size: "1024x1024",
  responseFormat: "url",
  estimatedCredits: 1,
});

for (const img of res.images) console.log(img.url);

Mobile tip: prefer responseFormat: "url". Base64 payloads can be megabytes and saturate the React Native bridge.

Image generation runs on its own daily credit bucket — exhausting chat tokens does not block image calls and vice versa. Only OpenAI-backed apps can call images.generate; Anthropic-backed apps return 400 unsupported_capability.

Image editing

Transform an existing image with a prompt — typical for portrait restyling, costume swaps, etc. Source image goes as base64 inside the JSON body (the platform's HTTP layer is JSON-only). On RN read the file with expo-file-system's readAsStringAsync(uri, { encoding: 'base64' }).

import * as FileSystem from "expo-file-system";

const base64 = await FileSystem.readAsStringAsync(playerPhotoUri, {
  encoding: FileSystem.EncodingType.Base64,
});

const res = await platform.llm.images.edit({
  model: "gpt-image-1",
  prompt: "Render this person as a high-fantasy character portrait, watercolor style",
  image: base64,
  imageMediaType: "image/jpeg",
  size: "1024x1024",
  responseFormat: "url",
  estimatedCredits: 1,
});

console.log(res.images[0].url);

Body limit: Lambda HTTP API caps body at ~6 MB after base64 — keep raw image ≲ 4.5 MB. Downscale phone photos before encoding.

Optional mask (base64 RGBA PNG, transparent pixels = the area to repaint). Same provider rules as generate: OpenAI only, separate credit bucket.

API

  • createPlatformClient(opts) → PlatformClient
  • platform.config.get() → AppConfigResponse
  • platform.llm.chat(req) → LlmChatResponse
  • platform.llm.chatStream(req, signal?) → AsyncGenerator<LlmStreamChunk>
  • platform.llm.object<T>(req) → LlmObjectResponse<T>
  • platform.llm.images.generate(req) → ImagesGenerateResponse
  • platform.llm.images.edit(req) → ImagesEditResponse
  • estimateImageTokens(image, provider, dimensions?) → number
  • fromZod(schema) from @amber-core/sdk-mobile/zod

The client injects Authorization: Bearer <token> and X-App-Id headers automatically. For non-streaming chat, object, images.generate, and images.edit, an X-Idempotency-Key is generated per call to make retries safe.

Migration from 0.1.0

LlmMessage now accepts content blocks alongside plain strings — old code ({ role: "user", content: "..." }) keeps working without changes. The streaming SSE delta event now carries multiple payload shapes discriminated by type ("delta" for text, "tool_use_*" for tool calls); existing code that reads chunk.content for chunk.type === "delta" is unchanged.