Result types
Every capability returns the same type: GenerationResult. It does not matter
whether you asked for text, an image, a video, speech, a sound effect, or music.
The bytes and the metadata are always in the same place, so one code path can
handle all six mediums.
The common shape
A GenerationResult has five fields:
bytes(Uint8List): the raw output, whatever the medium is.mimeType(String): the IANA MIME type ofbytes, for exampleimage/pngoraudio/mpeg.kind(MediaKind): which medium the bytes represent.metadata(GenerationMetadata): normalized fields the provider reported.seedUsed(String?): the seed the provider actually used, when it reports one back.
For text there is one extra getter. result.text decodes bytes as UTF-8, so
you do not have to decode by hand:
import 'package:ai_abstracted/ai_abstracted.dart';
// `result` is a GenerationResult you got back from a generator.
void inspect(GenerationResult result) {
print(result.kind); // e.g. MediaKind.image
print(result.mimeType); // e.g. image/png
print(result.metadata.model);
if (result.kind == MediaKind.text) {
print(result.text); // UTF-8 decode of result.bytes
}
}
The package never writes files. You persist bytes yourself, usually with
dart:io. That import lives in your app, not in this package, which stays free
of dart:io:
import 'dart:io';
import 'package:ai_abstracted/ai_abstracted.dart';
Future<void> save(GenerationResult result, String path) async {
await File(path).writeAsBytes(result.bytes);
}
GenerationMetadata has one required field, model. Every other field is
optional, so the same shape fits all six mediums. Providers fill in what they
report and leave the rest null. The sections below say which fields carry
meaning per medium.
Text
bytes is UTF-8 encoded text. The mimeType is usually text/plain. Read the
string with result.text rather than decoding bytes yourself.
Meaningful metadata: model is the provider model id that produced the
completion (for example claude-opus-4-8 or gemini-2.5-flash). Size and
duration fields do not apply.
import 'dart:io';
import 'package:ai_abstracted/ai_abstracted.dart';
Future<void> saveText(GenerationResult result) async {
// result.text is the UTF-8 string; result.bytes is the same content raw.
await File('completion.txt').writeAsBytes(result.bytes);
}
Image
bytes is an encoded image. The mimeType is typically image/png.
Meaningful metadata: width and height in pixels, when the provider reports
them. durationMs and hasAudio do not apply to a still image.
import 'dart:io';
import 'package:ai_abstracted/ai_abstracted.dart';
Future<void> saveImage(GenerationResult result) async {
final w = result.metadata.width;
final h = result.metadata.height;
print('image is ${w}x$h');
await File('image.png').writeAsBytes(result.bytes);
}
Video
bytes is an encoded video clip. The mimeType is typically video/mp4.
Meaningful metadata: durationMs (with metadata.duration as a Duration),
hasAudio for whether the clip carries an audio track, and width and
height. Veo 3 carries audio, and hasAudio reflects the withAudio flag you
set on the request.
import 'dart:io';
import 'package:ai_abstracted/ai_abstracted.dart';
Future<void> saveVideo(GenerationResult result) async {
print('duration: ${result.metadata.duration}');
print('has audio: ${result.metadata.hasAudio}');
await File('clip.mp4').writeAsBytes(result.bytes);
}
Speech
bytes is synthesized audio. SpeechRequest.format defaults to mp3, so the
mimeType is usually audio/mpeg.
Meaningful metadata: durationMs when the provider reports it (ElevenLabs does
not always report a duration). The voice id lands in metadata.extra under the
voice key. ElevenLabs voice ids are long strings from your voice library, not
names.
import 'dart:io';
import 'package:ai_abstracted/ai_abstracted.dart';
Future<void> saveSpeech(GenerationResult result) async {
final voice = result.metadata.extra['voice'];
print('spoken with voice: $voice');
await File('speech.mp3').writeAsBytes(result.bytes);
}
Sound effect
bytes is a short non-musical audio clip. SoundEffectRequest.format defaults
to mp3, so the mimeType is usually audio/mpeg.
Meaningful metadata: durationMs when reported. Provider-specific fields, if
any, come through metadata.extra.
import 'dart:io';
import 'package:ai_abstracted/ai_abstracted.dart';
Future<void> saveSoundEffect(GenerationResult result) async {
await File('sfx.mp3').writeAsBytes(result.bytes);
}
Music
bytes is a musical track. MusicRequest.format defaults to mp3, so the
mimeType is usually audio/mpeg.
Meaningful metadata: durationMs when reported, and providerJobId for the
long-running job that produced the track. Extra provider fields (for example a
returned title or style) come through metadata.extra.
import 'dart:io';
import 'package:ai_abstracted/ai_abstracted.dart';
Future<void> saveMusic(GenerationResult result) async {
print('job: ${result.metadata.providerJobId}');
await File('track.mp3').writeAsBytes(result.bytes);
}
Branch on the medium
MediaKind has two getters when you want to route by category instead of
checking each value. isAudio is true for speech, soundEffect, and music.
isVisual is true for image and video. Use them to pick a file extension or
a preview widget:
import 'package:ai_abstracted/ai_abstracted.dart';
String extensionFor(GenerationResult result) {
if (result.kind == MediaKind.text) return 'txt';
if (result.kind.isVisual) return result.kind == MediaKind.image ? 'png' : 'mp4';
if (result.kind.isAudio) return 'mp3';
return 'bin';
}