Skip to main content

wiro 0.1.1 copy "wiro: ^0.1.1" to clipboard
wiro: ^0.1.1 copied to clipboard

Official Wiro AI SDK for Dart and Flutter with typed model discovery, execution, file uploads, task polling, WebSocket streaming, cancellation, and structured errors.

Wiro AI

Wiro AI SDK for Dart and Flutter #

Run image, video, audio and LLM models from Dart and Flutter apps with the Wiro AI API: typed requests, live task tracking and file uploads.

pub package Dart 3.8+ Flutter 3.32.8+ CI License: MIT

Website · Models · Docs · Dashboard · pub.dev

wiro is the official Dart package for calling the Wiro AI model API from Flutter apps, Dart servers and command-line tools. It runs any model in the Wiro AI catalog, through typed request classes for popular image, video and music models or through Wiro.model with a parameter map, and follows each task by polling or over a WebSocket. The example folder has a Flutter text-to-image app and four command-line scripts.

Features #

  • Search and explore available AI models
  • Run popular models with compile-time checked typed requests
  • Read typed model input schemas
  • Validate dynamic parameters against model schemas
  • Run image, video, audio, and other generative models
  • Use typed model IDs, task tokens, and task IDs
  • Submit and await a sealed task result with one subscribe call
  • Send device files as model inputs with automatic uploads
  • Upload byte arrays or streams
  • Poll tasks or stream typed progress over WebSocket
  • Cancel queued tasks and stop running tasks
  • Typed errors for authentication, validation, rate limits, and networking
  • Request cancellation, timeouts, retry with exponential backoff, and logging
  • API key and signature-based authentication
  • Pure Dart core with Flutter, image, and video examples

Requirements #

The core package is pure Dart and supports Dart VM, Android, iOS, web, macOS, Windows, and Linux. Version 0.x may receive API refinements before the first stable release.

Installation #

dart pub add wiro

For Flutter applications:

flutter pub add wiro

Quick start #

Any model on Wiro AI runs through Wiro.model. Type Wiro. in your IDE to discover typed factories for popular models as well:

import 'package:wiro/wiro.dart';

Future<void> main() async {
  final client = WiroClient(apiKey: 'your-api-key');

  try {
    final result = await client.subscribeRequest(
      Wiro.model(
        'black-forest-labs/flux-2-pro',
        parameters: {
          'prompt': 'A cinematic mountain lake',
          'width': 1024,
          'height': 1024,
        },
      ),
    );
    print(result.task.outputs.first.url);
  } finally {
    client.close();
  }
}

Which call do I need? #

I want to... Call
Generate with a supported model client.subscribeRequest(Wiro.flux2Pro(...))
Run any other model client.subscribeRequest(Wiro.model('owner/model', parameters: {...}))
Find a model client.searchModels(search: '...') or client.explore()
See what parameters a model takes client.getModelSchema(...), then schema.validate(params)
Watch progress onUpdate: callback, or client.subscribeStream(...)
Send a device file to a model WiroFileInput.bytes(bytes, fileName: '...'), uploaded automatically
Cancel or stop a task client.cancelTask(...) / client.killTask(...)
Keep API keys off the device WiroClient.proxied(proxyUri: ...)
Handle failure switch on WiroTaskSuccess / WiroTaskFailure(:reason)

Run a model with a typed request #

Popular models ship with typed request classes, so required parameters and select options are checked at compile time and value ranges are checked by assertions in debug builds:

Compile-time checked typed request with Wiro.flux2Pro

final result = await client.subscribeRequest(
  Wiro.flux2Pro(
    prompt: 'A cinematic mountain lake at sunrise',
    width: 1024,
    height: 1024,
    outputFormat: WiroFlux2ProOutputFormat.png,
  ),
);

switch (result) {
  case WiroTaskSuccess(:final task):
    print(task.outputs.first.url);
  case WiroTaskFailure(:final reason):
    print('Failed: ${reason.name}');
}

Type Wiro. in your IDE to list every model with a typed request; each factory (such as Wiro.flux2Pro) returns the matching request class. Typed requests generated from the live Wiro AI schemas are included for:

Category Models
Image FLUX.2 Pro, GPT Image 2, Nano Banana Pro, Seedream v4, Grok Imagine Image
Video Runway Gen-4.5, Seedance 2.0, Kling V3, Veo 3.1, Hailuo 2.3 Fast, Grok Imagine Video
Music Lyria 3

Any other model runs through Wiro.model as shown below. runRequest is the non-tracking equivalent of subscribeRequest.

Typed requests are generated from the live Wiro AI model schemas with an internal tool, so coverage grows with SDK releases. Missing a model? Open an issue to request a typed binding, or implement WiroModelRequest yourself in the meantime.

Run any model with Wiro.model #

Models without a typed factory run through Wiro.model with a dynamic parameter map, so every model works even before the SDK ships a binding for it. Responses are fully typed.

final result = await client.subscribeRequest(
  Wiro.model(
    'black-forest-labs/flux-2-pro',
    parameters: {
      'prompt': 'A cinematic mountain lake at sunrise',
      'width': 1024,
      'height': 1024,
    },
  ),
  trackingMode: WiroTaskTrackingMode.webSocket,
  onUpdate: (update) {
    switch (update) {
      case WiroTaskEventUpdate(
        event: WiroSocketMessageEvent(
          payload: WiroProgressPayload(:final progress),
        ),
      ):
        print('${update.statusValue}: ${progress.percentage}');
      case _:
        print(update.statusValue);
    }
  },
);

for (final output in result.task.outputs) {
  print(output.url);
}

subscribe returns WiroTaskSuccess or WiroTaskFailure, both carrying the terminal task. trackingMode can be polling or webSocket; polling is the default. Both modes emit sealed WiroTaskUpdate variants. Failures expose a typed reason, and result.task.elapsed reports fractional task duration as a Duration.

Use the lower-level API when submission and tracking must be managed separately:

final run = await client.runModel(
  WiroModelId('google', 'veo3-1'),
  parameters: {
    'prompt': 'A cinematic mountain lake',
    'durationSeconds': '4',
  },
  callbackUrl: Uri.parse('https://example.com/wiro-webhook'),
);
final taskToken = run.taskToken;
if (taskToken == null) {
  throw StateError('Wiro did not return a task token.');
}
final task = await client.waitForTask(taskToken);

Server-derived model IDs, task IDs, task tokens, and socket event IDs are nullable when the Wiro AI API omits or returns invalid identifier fields. Identifiers created explicitly with WiroModelId, WiroTaskId, or WiroTaskToken remain strictly validated.

Get the accepted parameters before running a model:

final schema = await client.getModelSchema(
  WiroModelId('google', 'veo3-1'),
);
for (final parameter in schema.parameters) {
  print('${parameter.id}: ${parameter.runtimeType}');
}
schema.validate({
  'prompt': 'A cinematic mountain lake',
  'durationSeconds': '4',
});

Select, number, and text parameter variants expose typed defaultValue fields. Unknown variants preserve dynamic defaults, while file parameter defaults remain available through parameter.raw['default'].

File inputs and uploads #

File parameters take WiroFileInput values. Wrap an already-hosted file with WiroFileInput.url, or pass raw device bytes (a gallery pick, a camera shot) with WiroFileInput.bytes. The client uploads the bytes automatically and swaps in the URL before the model runs:

final result = await client.subscribeRequest(
  Wiro.model(
    'wiroai/flux-upscaler',
    parameters: {
      'inputImage': [
        WiroFileInput.bytes(
          await image.readAsBytes(),
          fileName: 'photo.jpg',
        ),
      ],
    },
  ),
);

print(result.task.outputs.first.url);

The same works inside dynamic parameter maps:

final run = await client.runModel(
  WiroModelId('google', 'veo3-1'),
  parameters: {
    'prompt': 'Animate this image',
    'inputImage': [
      WiroFileInput.url(Uri.parse('https://example.com/input.jpg')),
    ],
    'durationSeconds': '4',
  },
);

To manage uploads yourself, call client.uploadFile(bytes, fileName: ...) and pass the returned URL, or use uploadStream with an exact contentLength for large files. Streaming avoids SDK-side buffering on supported runtimes.

Observe task progress #

Pass onUpdate or use WebSocket tracking to watch a run live:

final result = await client.subscribeRequest(
  Wiro.runwayGen45(
    prompt: 'A drone shot over misty mountains',
    ratio: WiroRunwayGen45Ratio.landscape16x9,
    duration: 2,
  ),
  trackingMode: WiroTaskTrackingMode.webSocket,
  onUpdate: (update) {
    print(update.statusValue);
  },
);

Use subscribeStream when an await for loop is more convenient than a callback:

await for (final update in client.subscribeStream(
  WiroModelId('black-forest-labs', 'flux-2-pro'),
  parameters: {'prompt': 'A cinematic mountain lake'},
)) {
  print(update.statusValue);
}

Use WebSocket for realtime lifecycle, progress, LLM, and binary events:

await for (final event in client.watchTaskSocket(taskToken)) {
  switch (event) {
    case WiroSocketMessageEvent(
      :final statusValue,
      :final payload,
    ):
      switch (payload) {
        case WiroLogPayload(:final message):
          print('$statusValue: $message');
        case WiroProgressPayload(:final progress):
          print('$statusValue: ${progress.percentage}');
        case WiroOutputsPayload(:final outputs):
          for (final output in outputs) {
            print(output.url);
          }
        case WiroUnknownPayload():
          print('$statusValue: unknown payload');
      }
    case WiroSocketBinaryEvent(:final bytes):
      print('Received ${bytes.length} realtime bytes');
  }
}

The socket uses the Wiro AI task_info protocol and closes after task_postprocess_end or task_cancel. Binary frames are preserved for realtime audio models.

Polling remains available as a fallback:

await for (final task in client.watchTask(
  taskToken,
  timeout: const Duration(minutes: 10),
)) {
  print(task.statusValue);
}

For a definitive success check, fetch the terminal task and verify task.isSuccessful.

Cancellation #

final cancellationToken = WiroCancellationToken();

final request = client.searchModels(
  search: 'image',
  cancellationToken: cancellationToken,
);

cancellationToken.cancel();
await request;

Cancelled requests throw WiroRequestCancelledException. Cancelling a subscribeStream subscription also stops its active polling or WebSocket tracking without emitting a cancellation error.

Retries, timeouts, and logging #

final client = WiroClient(
  apiKey: 'your-api-key',
  requestTimeout: const Duration(seconds: 20),
  pollInterval: const Duration(seconds: 2),
  retryPolicy: const WiroRetryPolicy(
    maxRetries: 3,
    initialDelay: Duration(milliseconds: 500),
  ),
  logger: (event) {
    print('${event.level.name}: ${event.message}');
  },
);

Automatic retries apply only to safe read-like operations such as model and task lookup. Model runs and file uploads are not retried because they can create duplicate billable work. Rate-limit retries respect Retry-After.

Logs never contain API keys, secrets, headers, or request bodies. Malformed nested JSON ignored during response parsing is reported as a debug log event instead of through global diagnostics state.

Error handling #

try {
  await client.explore();
} on WiroApiResultException catch (error) {
  // HTTP succeeded, but Wiro rejected the operation.
  print('${error.code}: ${error.message}');
} on WiroAuthenticationException {
  // Invalid or expired credentials.
} on WiroRateLimitException catch (error) {
  print('Retry after: ${error.retryAfter}');
} on WiroSchemaValidationException catch (error) {
  print(error.issues);
} on WiroValidationException catch (error) {
  print(error.message);
} on WiroTimeoutException {
  // Request or task polling timed out.
} on WiroNetworkException {
  // The API could not be reached.
} on WiroUnknownApiException catch (error) {
  print('${error.statusCode}: ${error.message}');
}

The Wiro AI API can report application-level failures with HTTP 2xx and result: false. The SDK converts these responses to WiroApiResultException.

Authentication #

API key authentication:

final client = WiroClient(apiKey: 'your-api-key');

Signature-based authentication:

final client = WiroClient(
  apiKey: 'your-api-key',
  apiSecret: 'your-api-secret',
);

The configured credentials must match the authentication type selected for the Wiro AI project.

Never embed long-lived Wiro AI credentials in a production mobile application. Use a proxied client so credentials stay on your backend:

final client = WiroClient.proxied(
  proxyUri: Uri.parse('https://your-backend.example.com/wiro'),
  headers: {'Authorization': 'Bearer $sessionToken'},
);

The device sends requests to your proxy without any Wiro AI auth headers. The proxy must accept the same REST paths as the Wiro AI API, attach x-api-key (and signature headers when required), and forward the request to https://api.wiro.ai/v1. Task WebSocket streams still connect directly to Wiro AI because they authenticate with per-task tokens instead of API credentials.

Examples #

The example directory contains:

  • A Flutter iOS and Android text-to-image app built on Wiro.flux2Pro
  • bin/generate_image.dart using Flux 2 Pro
  • bin/generate_video.dart using Runway Gen-4.5 with streamed task progress
  • bin/generate_dynamic.dart running a model through Wiro.model after schema.validate
  • bin/upscale_image.dart sending a file input from a public URL or local bytes
cd example

WIRO_API_KEY=your-api-key dart run bin/generate_image.dart
WIRO_API_KEY=your-api-key dart run bin/generate_video.dart
WIRO_API_KEY=your-api-key dart run bin/generate_dynamic.dart
WIRO_API_KEY=your-api-key dart run bin/upscale_image.dart ./photo.jpg

flutter run --dart-define=WIRO_API_KEY=your-api-key

The dart-define approach is only for local demonstration. Build-time values can be extracted from a distributed mobile application.

Documentation #

More from Wiro AI #

Repository What it is
Wiro-MCP Official MCP server for Wiro AI, @wiro-ai/wiro-mcp on npm
Wiro-Plugin Wiro AI plugin for Cursor, Claude Code, Gemini CLI and Devin
Swift-QuickStart WiroKit, the Wiro AI SDK for iOS
Kotlin-QuickStart WiroKit, the Wiro AI SDK for Android
ReactNative-QuickStart Wiro AI SDK for React Native and Expo, @wiro-ai/wiro-react-native on npm
Swift-Realtime Realtime voice AI example for iOS
Kotlin-Realtime Realtime voice AI example for Android
Dart-Realtime Realtime voice AI example for Flutter
ReactNative-Realtime Realtime voice AI example for React Native
n8n-nodes-wiroai Wiro AI nodes for n8n, @wiro-ai/n8n-nodes-wiroai on npm
Wiro-Docs Source of the Wiro AI API docs

Website · Models · Docs · X

License #

MIT. See LICENSE.

Wiro AI koala mascot
0
likes
160
points
--
downloads

Documentation

Documentation
API reference

Publisher

verified publisherwiro.ai

Weekly Downloads

Official Wiro AI SDK for Dart and Flutter with typed model discovery, execution, file uploads, task polling, WebSocket streaming, cancellation, and structured errors.

Homepage
Repository (GitHub)
View/report issues
Contributing

Topics

#ai #generative-ai #wiro #api-client

License

MIT (license)

Dependencies

crypto, http, web_socket_channel

More

Packages that depend on wiro