infro 0.1.3
infro: ^0.1.3 copied to clipboard
The official Dart client for the INFRO API — one endpoint for text, image, video and audio models, with routing, fallbacks and estimated provider cost.
// Run with:
//
// INFRO_API_KEY=sk_infro_... dart run example/infro_example.dart
//
// Four calls, one per thing this client exists for that an OpenAI-compatible
// client cannot express: a cost figure on an image, a video render as an async
// job, the connection that served a request, and where the key stands against
// its own credit limit.
import 'dart:io';
import 'package:infro/infro.dart';
Future<void> main() async {
// The key falls back to INFRO_API_KEY, so it is never in this file.
final infro = Infro();
try {
// Text. `provider` and `connection` name your own connection that served;
// `route` is its position in the chain — "primary", "standby_a".
final completion = await infro.chat.completions.create(
model: 'anthropic/claude-sonnet-5',
messages: [
{'role': 'user', 'content': 'Name one thing fog is good for.'},
],
);
stdout.writeln(completion['choices'][0]['message']['content']);
stdout.writeln('served by ${completion['provider']} '
'(${completion['connection']}), route ${completion['route']}');
// Streaming is a Stream, which is what a Flutter widget already consumes.
// It throws rather than completing quietly if the gateway closes without
// [DONE] — truncated text that looks complete is worse than an error.
final stream = infro.chat.completions.createStream(
model: 'anthropic/claude-sonnet-5',
messages: [
{'role': 'user', 'content': 'Write a haiku about fog.'},
],
);
await for (final chunk in stream) {
stdout.write(chunk['choices'][0]['delta']['content'] ?? '');
}
stdout.writeln();
// An image, with what it cost on the same response.
final image = await infro.images.generate(
model: 'bfl/flux-2-pro',
prompt: 'a lighthouse in fog, 35mm',
);
stdout.writeln(image['data'][0]['url']);
stdout.writeln('cost: \$${image['usage']['cost']}');
// Video is an async job: submit, then wait. `wait` polls to a deadline and
// says the job may still be running rather than pretending it failed.
final job = await infro.videos.create(
model: 'google/veo-3.1',
prompt: 'a lighthouse beam sweeping through fog',
durationSeconds: 8,
);
stdout.writeln('job ${job['id']} is ${job['status']}');
final finished = await infro.jobs.waitFor(
job['id'] as String,
timeout: const Duration(minutes: 10),
);
stdout.writeln(finished['output'][0]['url']);
// Where the key stands against its own credit limit. `limit` is null when
// the key has none; `usage` is estimated provider spend in the window.
final key = await infro.keys.retrieve();
stdout.writeln('used \$${key['usage']} of '
'${key['limit'] == null ? 'no limit' : '\$${key['limit']}'}');
if (key['limit_period'] != null) {
stdout.writeln('limit period: ${key['limit_period']}'
'${key['limit_resets_at'] == null ? '' : ' (next ${key['limit_resets_at']})'}');
}
} on BudgetExceededException catch (e) {
// The taxonomy is types, so this is a branch rather than a string match.
// A budget you set was reached: raise it or wait for its period to reset.
stderr.writeln('budget reached: ${e.message}');
exitCode = 1;
} on RateLimitException catch (e) {
stderr.writeln('slow down: ${e.message}');
exitCode = 1;
} on InfroApiException catch (e) {
stderr.writeln('${e.status} ${e.type}: ${e.message}');
exitCode = 1;
} finally {
infro.close();
}
}