pub const STREAMING_VIRTUAL_FIELDS: &[&str] = &[
"chunks",
"chunks.length",
"stream_content",
"stream_complete",
"no_chunks_after_done",
"tool_calls",
"finish_reason",
];
const STREAMING_VIRTUAL_ROOTS: &[&str] = &["tool_calls", "finish_reason"];
pub fn is_streaming_virtual_field(field: &str) -> bool {
if STREAMING_VIRTUAL_FIELDS.contains(&field) {
return true;
}
for root in STREAMING_VIRTUAL_ROOTS {
if field.len() > root.len() && field.starts_with(root) {
let rest = &field[root.len()..];
if rest.starts_with('[') || rest.starts_with('.') {
return true;
}
}
}
false
}
fn split_streaming_deep_path(field: &str) -> Option<(&str, &str)> {
for root in STREAMING_VIRTUAL_ROOTS {
if field.len() > root.len() && field.starts_with(root) {
let rest = &field[root.len()..];
if rest.starts_with('[') || rest.starts_with('.') {
return Some((root, rest));
}
}
}
None
}
const STREAMING_ONLY_AUTO_DETECT_FIELDS: &[&str] = &[
"chunks",
"chunks.length",
"stream_content",
"stream_complete",
"no_chunks_after_done",
];
pub fn resolve_is_streaming(fixture: &crate::fixture::Fixture, call_streaming: Option<bool>) -> bool {
if let Some(forced) = call_streaming {
return forced;
}
fixture.is_streaming_mock()
|| fixture.assertions.iter().any(|a| {
a.field
.as_deref()
.is_some_and(|f| !f.is_empty() && STREAMING_ONLY_AUTO_DETECT_FIELDS.contains(&f))
})
}
pub struct StreamingFieldResolver;
impl StreamingFieldResolver {
pub fn accessor(field: &str, lang: &str, chunks_var: &str) -> Option<String> {
match field {
"chunks" => Some(match lang {
"zig" => format!("{chunks_var}.items"),
"php" => format!("${chunks_var}"),
_ => chunks_var.to_string(),
}),
"chunks.length" => Some(match lang {
"rust" => format!("{chunks_var}.len()"),
"go" => format!("len({chunks_var})"),
"python" => format!("len({chunks_var})"),
"php" => format!("count(${chunks_var})"),
"elixir" => format!("length({chunks_var})"),
"kotlin" => format!("{chunks_var}.size"),
"zig" => format!("{chunks_var}.items.len"),
"swift" => format!("{chunks_var}.count"),
_ => format!("{chunks_var}.length"),
}),
"stream_content" => Some(match lang {
"rust" => {
format!(
"{chunks_var}.iter().map(|c| c.choices.first().and_then(|ch| ch.delta.content.as_deref()).unwrap_or(\"\")).collect::<String>()"
)
}
"go" => {
format!(
"func() string {{ var s string; for _, c := range {chunks_var} {{ if len(c.Choices) > 0 && c.Choices[0].Delta.Content != nil {{ s += *c.Choices[0].Delta.Content }} }}; return s }}()"
)
}
"java" => {
format!(
"{chunks_var}.stream().map(c -> c.choices().stream().findFirst().map(ch -> ch.delta().content() != null ? ch.delta().content() : \"\").orElse(\"\")).collect(java.util.stream.Collectors.joining())"
)
}
"php" => {
format!("implode('', array_map(fn($c) => $c->choices[0]->delta->content ?? '', ${chunks_var}))")
}
"kotlin" => {
format!(
"{chunks_var}.joinToString(\"\") {{ it.choices()?.firstOrNull()?.delta()?.content() ?: \"\" }}"
)
}
"elixir" => {
format!(
"{chunks_var} |> Enum.map(fn c -> (Enum.at(c.choices, 0) || %{{}}) |> Map.get(:delta, %{{}}) |> Map.get(:content, \"\") end) |> Enum.join(\"\")"
)
}
"python" => {
format!("\"\".join(c.choices[0].delta.content or \"\" for c in {chunks_var} if c.choices)")
}
"zig" => {
format!("{chunks_var}_content.items")
}
"swift" => {
format!(
"{chunks_var}.map {{ c in c.choices().first.flatMap {{ ch in ch.delta().content()?.toString() }} ?? \"\" }}.joined()"
)
}
_ => {
format!("{chunks_var}.map((c: any) => c.choices?.[0]?.delta?.content ?? '').join('')")
}
}),
"stream_complete" => Some(match lang {
"rust" => {
format!(
"{chunks_var}.last().and_then(|c| c.choices.first()).and_then(|ch| ch.finish_reason.as_ref()).is_some()"
)
}
"go" => {
format!(
"func() bool {{ if len({chunks_var}) == 0 {{ return false }}; last := {chunks_var}[len({chunks_var})-1]; return len(last.Choices) > 0 && last.Choices[0].FinishReason != nil }}()"
)
}
"java" => {
format!(
"!{chunks_var}.isEmpty() && {chunks_var}.get({chunks_var}.size()-1).choices().stream().findFirst().flatMap(ch -> java.util.Optional.ofNullable(ch.finishReason())).isPresent()"
)
}
"php" => {
format!("!empty(${chunks_var}) && isset(end(${chunks_var})->choices[0]->finishReason)")
}
"kotlin" => {
format!(
"{chunks_var}.isNotEmpty() && {chunks_var}.last().choices()?.firstOrNull()?.finishReason() != null"
)
}
"python" => {
format!("bool({chunks_var}) and {chunks_var}[-1].choices[0].finish_reason is not None")
}
"elixir" => {
format!("Enum.at(List.last({chunks_var}).choices, 0).finish_reason != nil")
}
"zig" => {
format!("{chunks_var}.items.len > 0")
}
"swift" => {
format!("!{chunks_var}.isEmpty && {chunks_var}.last!.choices().first?.finish_reason() != nil")
}
_ => {
format!(
"{chunks_var}.length > 0 && {chunks_var}[{chunks_var}.length - 1].choices?.[0]?.finishReason != null"
)
}
}),
"no_chunks_after_done" => Some(match lang {
"rust" => "true".to_string(),
"go" => "true".to_string(),
"java" => "true".to_string(),
"php" => "true".to_string(),
_ => "true".to_string(),
}),
"tool_calls" => Some(match lang {
"rust" => {
format!(
"{chunks_var}.iter().flat_map(|c| c.choices.iter().flat_map(|ch| ch.delta.tool_calls.iter().flatten())).collect::<Vec<_>>()"
)
}
"go" => {
format!(
"func() []pkg.StreamToolCall {{ var tc []pkg.StreamToolCall; for _, c := range {chunks_var} {{ for _, ch := range c.Choices {{ tc = append(tc, ch.Delta.ToolCalls...) }} }}; return tc }}()"
)
}
"java" => {
format!(
"{chunks_var}.stream().flatMap(c -> c.choices().stream()).flatMap(ch -> ch.delta().toolCalls() != null ? ch.delta().toolCalls().stream() : java.util.stream.Stream.empty()).toList()"
)
}
"php" => {
format!(
"array_merge(...array_map(fn($c) => $c->choices[0]->delta->toolCalls ?? [], ${chunks_var}))"
)
}
"kotlin" => {
format!(
"{chunks_var}.flatMap {{ c -> c.choices()?.flatMap {{ ch -> ch.delta()?.toolCalls() ?: emptyList() }} ?: emptyList() }}"
)
}
"python" => {
format!(
"[t for c in {chunks_var} for ch in (c.choices or []) for t in (ch.delta.tool_calls or [])]"
)
}
"elixir" => {
format!(
"{chunks_var} |> Enum.flat_map(fn c -> (List.first(c.choices) || %{{}}).delta |> Map.get(:tool_calls, []) end)"
)
}
"zig" => {
format!("{chunks_var}.items")
}
"swift" => {
format!(
"{chunks_var}.flatMap {{ c in c.choices().first.map {{ ch in ch.delta().tool_calls().map {{ Array($0) }} ?? [] }} ?? [] }}"
)
}
_ => {
format!("{chunks_var}.flatMap((c: any) => c.choices?.[0]?.delta?.toolCalls ?? [])")
}
}),
"finish_reason" => Some(match lang {
"rust" => {
format!(
"{chunks_var}.last().and_then(|c| c.choices.first()).and_then(|ch| ch.finish_reason.as_ref()).map(|v| v.to_string()).unwrap_or_default()"
)
}
"go" => {
format!(
"func() string {{ if len({chunks_var}) == 0 {{ return \"\" }}; last := {chunks_var}[len({chunks_var})-1]; if len(last.Choices) > 0 && last.Choices[0].FinishReason != nil {{ return string(*last.Choices[0].FinishReason) }}; return \"\" }}()"
)
}
"java" => {
format!(
"({chunks_var}.isEmpty() ? null : {chunks_var}.get({chunks_var}.size()-1).choices().stream().findFirst().map(ch -> ch.finishReason() == null ? null : ch.finishReason().getValue()).orElse(null))"
)
}
"php" => {
format!("(!empty(${chunks_var}) ? (end(${chunks_var})->choices[0]->finishReason ?? null) : null)")
}
"kotlin" => {
format!(
"(if ({chunks_var}.isEmpty()) null else {chunks_var}.last().choices()?.firstOrNull()?.finishReason()?.getValue())"
)
}
"python" => {
format!(
"(str({chunks_var}[-1].choices[0].finish_reason) if {chunks_var} and {chunks_var}[-1].choices else None)"
)
}
"elixir" => {
format!("Enum.at(List.last({chunks_var}).choices, 0).finish_reason")
}
"zig" => {
format!(
"(blk: {{ if ({chunks_var}.items.len == 0) break :blk \"\"; var _lcp = std.json.parseFromSlice(std.json.Value, std.heap.c_allocator, {chunks_var}.items[{chunks_var}.items.len - 1], .{{}}) catch break :blk \"\"; defer _lcp.deinit(); if (_lcp.value.object.get(\"choices\")) |_lchs| if (_lchs.array.items.len > 0) if (_lchs.array.items[0].object.get(\"finish_reason\")) |_fr| if (_fr == .string) break :blk _fr.string; break :blk \"\"; }})"
)
}
"swift" => {
format!(
"({chunks_var}.isEmpty ? nil : {chunks_var}.last!.choices().first?.finish_reason()?.toString())"
)
}
_ => {
format!(
"{chunks_var}.length > 0 ? {chunks_var}[{chunks_var}.length - 1].choices?.[0]?.finishReason : undefined"
)
}
}),
"usage" => Some(match lang {
"python" => {
format!("({chunks_var}[-1].usage if {chunks_var} else None)")
}
"rust" => {
format!("{chunks_var}.last().and_then(|c| c.usage.as_ref())")
}
"go" => {
format!(
"func() interface{{}} {{ if len({chunks_var}) == 0 {{ return nil }}; return {chunks_var}[len({chunks_var})-1].Usage }}()"
)
}
"java" => {
format!("({chunks_var}.isEmpty() ? null : {chunks_var}.get({chunks_var}.size()-1).usage())")
}
"kotlin" => {
format!("(if ({chunks_var}.isEmpty()) null else {chunks_var}.last().usage())")
}
"php" => {
format!("(!empty(${chunks_var}) ? end(${chunks_var})->usage ?? null : null)")
}
"elixir" => {
format!("(if length({chunks_var}) > 0, do: List.last({chunks_var}).usage, else: nil)")
}
"swift" => {
format!("({chunks_var}.isEmpty ? nil : {chunks_var}.last!.usage())")
}
_ => {
format!("({chunks_var}.length > 0 ? {chunks_var}[{chunks_var}.length - 1].usage : undefined)")
}
}),
_ => {
if let Some((root, tail)) = split_streaming_deep_path(field) {
if lang == "rust" && root == "tool_calls" {
return Some(render_rust_tool_calls_deep(chunks_var, tail));
}
if lang == "zig" && root == "tool_calls" {
return None;
}
let root_expr = Self::accessor(root, lang, chunks_var)?;
Some(render_deep_tail(&root_expr, tail, lang))
} else {
None
}
}
}
}
pub fn collect_snippet(lang: &str, stream_var: &str, chunks_var: &str) -> Option<String> {
match lang {
"rust" => Some(format!(
"let {chunks_var}: Vec<_> = tokio_stream::StreamExt::collect::<Vec<_>>({stream_var}).await\n .into_iter()\n .map(|r| r.expect(\"stream item failed\"))\n .collect();"
)),
"go" => Some(format!(
"var {chunks_var} []pkg.ChatCompletionChunk\n\tfor chunk := range {stream_var} {{\n\t\t{chunks_var} = append({chunks_var}, chunk)\n\t}}"
)),
"java" => Some(format!(
"var {chunks_var} = new java.util.ArrayList<ChatCompletionChunk>();\n var _it = {stream_var};\n while (_it.hasNext()) {{ {chunks_var}.add(_it.next()); }}"
)),
"php" => Some(format!(
"${chunks_var} = is_string(${stream_var}) ? (json_decode(${stream_var}) ?: []) : iterator_to_array(${stream_var});"
)),
"python" => Some(format!(
"{chunks_var} = []\n async for chunk in {stream_var}:\n {chunks_var}.append(chunk)"
)),
"kotlin" => {
Some(format!("val {chunks_var} = {stream_var}.asSequence().toList()"))
}
"elixir" => Some(format!("{chunks_var} = Enum.to_list({stream_var})")),
"node" | "wasm" | "typescript" => Some(format!(
"const {chunks_var}: any[] = [];\n for await (const _chunk of {stream_var}) {{ {chunks_var}.push(_chunk); }}"
)),
"swift" => {
Some(format!(
"var {chunks_var}: [ChatCompletionChunk] = []\n for try await _chunk in {stream_var} {{ {chunks_var}.append(_chunk) }}"
))
}
"zig" => Some(Self::collect_snippet_zig(stream_var, chunks_var, "module", "ffi")),
_ => None,
}
}
pub fn collect_snippet_zig(stream_var: &str, chunks_var: &str, module_name: &str, ffi_prefix: &str) -> String {
let stream_next = format!("{ffi_prefix}_default_client_chat_stream_next");
let chunk_to_json = format!("{ffi_prefix}_chat_completion_chunk_to_json");
let chunk_free = format!("{ffi_prefix}_chat_completion_chunk_free");
let free_string = format!("{ffi_prefix}_free_string");
format!(
concat!(
"var {chunks_var}: std.ArrayList([]u8) = .empty;
",
" defer {{
",
" for ({chunks_var}.items) |_cj| std.heap.c_allocator.free(_cj);
",
" {chunks_var}.deinit(std.heap.c_allocator);
",
" }}
",
" var {chunks_var}_content: std.ArrayList(u8) = .empty;
",
" defer {chunks_var}_content.deinit(std.heap.c_allocator);
",
" while (true) {{
",
" const _nc = {module_name}.c.{stream_next}({stream_var});
",
" if (_nc == null) break;
",
" const _np = {module_name}.c.{chunk_to_json}(_nc);
",
" {module_name}.c.{chunk_free}(_nc);
",
" if (_np == null) continue;
",
" const _ns = std.mem.span(_np);
",
" const _nj = try std.heap.c_allocator.dupe(u8, _ns);
",
" {module_name}.c.{free_string}(_np);
",
" if (std.json.parseFromSlice(std.json.Value, std.heap.c_allocator, _nj, .{{}})) |_cp| {{
",
" defer _cp.deinit();
",
" if (_cp.value.object.get(\"choices\")) |_chs|
",
" if (_chs.array.items.len > 0)
",
" if (_chs.array.items[0].object.get(\"delta\")) |_dl|
",
" if (_dl.object.get(\"content\")) |_ct|
",
" if (_ct == .string) try {chunks_var}_content.appendSlice(std.heap.c_allocator, _ct.string);
",
" }} else |_| {{}}
",
" try {chunks_var}.append(std.heap.c_allocator, _nj);
",
" }}"
),
chunks_var = chunks_var,
stream_var = stream_var,
module_name = module_name,
stream_next = stream_next,
chunk_to_json = chunk_to_json,
chunk_free = chunk_free,
free_string = free_string,
)
}
}
fn render_rust_tool_calls_deep(chunks_var: &str, tail: &str) -> String {
let segs = parse_tail(tail);
let idx = segs.iter().find_map(|s| match s {
TailSeg::Index(n) => Some(*n),
_ => None,
});
let field_segs: Vec<&str> = segs
.iter()
.filter_map(|s| match s {
TailSeg::Field(f) => Some(f.as_str()),
_ => None,
})
.collect();
let base = format!(
"{chunks_var}.iter().flat_map(|c| c.choices.iter().flat_map(|ch| ch.delta.tool_calls.iter().flatten()))"
);
let with_nth = match idx {
Some(n) => format!("{base}.nth({n})"),
None => base,
};
let mut expr = with_nth;
for (i, f) in field_segs.iter().enumerate() {
let is_leaf = i == field_segs.len() - 1;
if is_leaf {
expr = format!("{expr}.and_then(|x| x.{f}.as_deref())");
} else {
expr = format!("{expr}.and_then(|x| x.{f}.as_ref())");
}
}
format!("{expr}.unwrap_or(\"\")")
}
#[derive(Debug, PartialEq)]
enum TailSeg {
Index(usize),
Field(String),
}
fn parse_tail(tail: &str) -> Vec<TailSeg> {
let mut segs = Vec::new();
let mut rest = tail;
while !rest.is_empty() {
if let Some(inner) = rest.strip_prefix('[') {
if let Some(close) = inner.find(']') {
let idx_str = &inner[..close];
if let Ok(idx) = idx_str.parse::<usize>() {
segs.push(TailSeg::Index(idx));
}
rest = &inner[close + 1..];
} else {
break;
}
} else if let Some(inner) = rest.strip_prefix('.') {
let end = inner.find(['.', '[']).unwrap_or(inner.len());
segs.push(TailSeg::Field(inner[..end].to_string()));
rest = &inner[end..];
} else {
break;
}
}
segs
}
fn render_deep_tail(root_expr: &str, tail: &str, lang: &str) -> String {
use heck::{ToLowerCamelCase, ToPascalCase};
let segs = parse_tail(tail);
let mut out = root_expr.to_string();
for seg in &segs {
match (seg, lang) {
(TailSeg::Index(n), "rust") => {
out = format!("({out})[{n}]");
}
(TailSeg::Index(n), "java") => {
out = format!("({out}).get({n})");
}
(TailSeg::Index(n), "kotlin") => {
if *n == 0 {
out = format!("({out}).first()");
} else {
out = format!("({out}).get({n})");
}
}
(TailSeg::Index(n), "elixir") => {
out = format!("Enum.at({out}, {n})");
}
(TailSeg::Index(n), "zig") => {
out = format!("({out}).items[{n}]");
}
(TailSeg::Index(n), "php") => {
out = format!("({out})[{n}]");
}
(TailSeg::Index(n), _) => {
out = format!("({out})[{n}]");
}
(TailSeg::Field(f), "rust") => {
use heck::ToSnakeCase;
out.push('.');
out.push_str(&f.to_snake_case());
}
(TailSeg::Field(f), "go") => {
use alef_codegen::naming::to_go_name;
out.push('.');
out.push_str(&to_go_name(f));
}
(TailSeg::Field(f), "java") => {
out.push('.');
out.push_str(&f.to_lower_camel_case());
out.push_str("()");
}
(TailSeg::Field(f), "kotlin") => {
out.push_str("?.");
out.push_str(&f.to_lower_camel_case());
out.push_str("()");
}
(TailSeg::Field(f), "csharp") => {
out.push('.');
out.push_str(&f.to_pascal_case());
}
(TailSeg::Field(f), "php") => {
out.push_str("->");
out.push_str(f);
}
(TailSeg::Field(f), "elixir") => {
out.push('.');
out.push_str(f);
}
(TailSeg::Field(f), "zig") => {
out.push('.');
out.push_str(f);
}
(TailSeg::Field(f), "python") | (TailSeg::Field(f), "ruby") => {
out.push('.');
out.push_str(f);
}
(TailSeg::Field(f), _) => {
out.push('.');
out.push_str(&f.to_lower_camel_case());
}
}
}
out
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn is_streaming_virtual_field_recognizes_all_fields() {
for field in STREAMING_VIRTUAL_FIELDS {
assert!(
is_streaming_virtual_field(field),
"field '{field}' not recognized as streaming virtual"
);
}
}
#[test]
fn is_streaming_virtual_field_rejects_real_fields() {
assert!(!is_streaming_virtual_field("content"));
assert!(!is_streaming_virtual_field("choices"));
assert!(!is_streaming_virtual_field("model"));
assert!(!is_streaming_virtual_field(""));
}
#[test]
fn is_streaming_virtual_field_rejects_non_root_paths_with_matching_tail() {
assert!(!is_streaming_virtual_field("choices[0].finish_reason"));
assert!(!is_streaming_virtual_field("choices[0].message.content"));
assert!(!is_streaming_virtual_field("data[0].embedding"));
}
#[test]
fn is_streaming_virtual_field_does_not_match_usage() {
assert!(!is_streaming_virtual_field("usage"));
assert!(!is_streaming_virtual_field("usage.total_tokens"));
assert!(!is_streaming_virtual_field("usage.prompt_tokens"));
}
#[test]
fn accessor_chunks_returns_var_name() {
assert_eq!(
StreamingFieldResolver::accessor("chunks", "rust", "chunks"),
Some("chunks".to_string())
);
assert_eq!(
StreamingFieldResolver::accessor("chunks", "node", "chunks"),
Some("chunks".to_string())
);
}
#[test]
fn accessor_chunks_length_uses_language_idiom() {
let rust = StreamingFieldResolver::accessor("chunks.length", "rust", "chunks").unwrap();
assert!(rust.contains(".len()"), "rust: {rust}");
let go = StreamingFieldResolver::accessor("chunks.length", "go", "chunks").unwrap();
assert!(go.starts_with("len("), "go: {go}");
let node = StreamingFieldResolver::accessor("chunks.length", "node", "chunks").unwrap();
assert!(node.contains(".length"), "node: {node}");
let php = StreamingFieldResolver::accessor("chunks.length", "php", "chunks").unwrap();
assert!(php.starts_with("count("), "php: {php}");
}
#[test]
fn accessor_chunks_length_zig_uses_items_len() {
let zig = StreamingFieldResolver::accessor("chunks.length", "zig", "chunks").unwrap();
assert_eq!(zig, "chunks.items.len", "zig chunks.length: {zig}");
}
#[test]
fn accessor_stream_content_zig_uses_content_items() {
let zig = StreamingFieldResolver::accessor("stream_content", "zig", "chunks").unwrap();
assert_eq!(zig, "chunks_content.items", "zig stream_content: {zig}");
}
#[test]
fn collect_snippet_zig_drains_via_ffi() {
let snip = StreamingFieldResolver::collect_snippet("zig", "_stream_handle", "chunks").unwrap();
assert!(snip.contains("std.ArrayList([]u8)"), "zig collect: {snip}");
assert!(snip.contains("chat_stream_next(_stream_handle)"), "zig collect: {snip}");
assert!(snip.contains("chunks_content"), "zig collect: {snip}");
assert!(
snip.contains("chunks.append(std.heap.c_allocator"),
"zig collect: {snip}"
);
assert!(snip.contains(".empty;"), "zig collect (Zig 0.16 unmanaged): {snip}");
}
#[test]
fn accessor_stream_content_rust_uses_iterator() {
let expr = StreamingFieldResolver::accessor("stream_content", "rust", "chunks").unwrap();
assert!(expr.contains(".collect::<String>()"), "rust stream_content: {expr}");
}
#[test]
fn accessor_no_chunks_after_done_returns_true() {
for lang in ["rust", "go", "java", "php", "node", "wasm", "elixir"] {
let expr = StreamingFieldResolver::accessor("no_chunks_after_done", lang, "chunks").unwrap();
assert_eq!(expr, "true", "lang {lang}: expected 'true', got '{expr}'");
}
}
#[test]
fn accessor_elixir_chunks_length_uses_length_function() {
let expr = StreamingFieldResolver::accessor("chunks.length", "elixir", "chunks").unwrap();
assert_eq!(expr, "length(chunks)", "elixir chunks.length: {expr}");
}
#[test]
fn accessor_elixir_stream_content_uses_pipe() {
let expr = StreamingFieldResolver::accessor("stream_content", "elixir", "chunks").unwrap();
assert!(expr.contains("|> Enum.join"), "elixir stream_content: {expr}");
assert!(expr.contains("|> Enum.map"), "elixir stream_content: {expr}");
assert!(
!expr.contains("choices[0]"),
"elixir stream_content must not use bracket access on list: {expr}"
);
assert!(
expr.contains("Enum.at("),
"elixir stream_content must use Enum.at for list index: {expr}"
);
}
#[test]
fn accessor_elixir_stream_complete_uses_list_last() {
let expr = StreamingFieldResolver::accessor("stream_complete", "elixir", "chunks").unwrap();
assert!(expr.contains("List.last(chunks)"), "elixir stream_complete: {expr}");
assert!(expr.contains("finish_reason != nil"), "elixir stream_complete: {expr}");
assert!(
!expr.contains("choices[0]"),
"elixir stream_complete must not use bracket access on list: {expr}"
);
assert!(
expr.contains("Enum.at("),
"elixir stream_complete must use Enum.at for list index: {expr}"
);
}
#[test]
fn accessor_elixir_finish_reason_uses_list_last() {
let expr = StreamingFieldResolver::accessor("finish_reason", "elixir", "chunks").unwrap();
assert!(expr.contains("List.last(chunks)"), "elixir finish_reason: {expr}");
assert!(expr.contains("finish_reason"), "elixir finish_reason: {expr}");
assert!(
!expr.contains("choices[0]"),
"elixir finish_reason must not use bracket access on list: {expr}"
);
assert!(
expr.contains("Enum.at("),
"elixir finish_reason must use Enum.at for list index: {expr}"
);
}
#[test]
fn collect_snippet_elixir_uses_enum_to_list() {
let snip = StreamingFieldResolver::collect_snippet("elixir", "result", "chunks").unwrap();
assert!(snip.contains("Enum.to_list(result)"), "elixir: {snip}");
assert!(snip.contains("chunks ="), "elixir: {snip}");
}
#[test]
fn collect_snippet_rust_uses_tokio_stream() {
let snip = StreamingFieldResolver::collect_snippet("rust", "result", "chunks").unwrap();
assert!(snip.contains("tokio_stream::StreamExt::collect"), "rust: {snip}");
assert!(snip.contains("let chunks"), "rust: {snip}");
assert!(snip.contains(".expect("), "rust must unwrap Result items: {snip}");
}
#[test]
fn collect_snippet_go_drains_channel() {
let snip = StreamingFieldResolver::collect_snippet("go", "stream", "chunks").unwrap();
assert!(snip.contains("for chunk := range stream"), "go: {snip}");
}
#[test]
fn collect_snippet_java_uses_iterator() {
let snip = StreamingFieldResolver::collect_snippet("java", "result", "chunks").unwrap();
assert!(snip.contains("hasNext()"), "java: {snip}");
}
#[test]
fn collect_snippet_php_decodes_json_or_iterates() {
let snip = StreamingFieldResolver::collect_snippet("php", "result", "chunks").unwrap();
assert!(snip.contains("json_decode"), "php must decode JSON: {snip}");
assert!(
snip.contains("iterator_to_array"),
"php must keep iterator_to_array fallback: {snip}"
);
assert!(snip.contains("$chunks ="), "php must bind $chunks: {snip}");
}
#[test]
fn collect_snippet_node_uses_for_await() {
let snip = StreamingFieldResolver::collect_snippet("node", "result", "chunks").unwrap();
assert!(snip.contains("for await"), "node: {snip}");
}
#[test]
fn collect_snippet_python_uses_async_for() {
let snip = StreamingFieldResolver::collect_snippet("python", "result", "chunks").unwrap();
assert!(snip.contains("async for chunk in result"), "python: {snip}");
assert!(snip.contains("chunks.append(chunk)"), "python: {snip}");
}
#[test]
fn accessor_stream_content_python_uses_join() {
let expr = StreamingFieldResolver::accessor("stream_content", "python", "chunks").unwrap();
assert!(expr.contains("\"\".join("), "python stream_content: {expr}");
assert!(expr.contains("c.choices"), "python stream_content: {expr}");
}
#[test]
fn accessor_stream_complete_python_uses_finish_reason() {
let expr = StreamingFieldResolver::accessor("stream_complete", "python", "chunks").unwrap();
assert!(
expr.contains("finish_reason is not None"),
"python stream_complete: {expr}"
);
}
#[test]
fn accessor_finish_reason_python_uses_last_chunk() {
let expr = StreamingFieldResolver::accessor("finish_reason", "python", "chunks").unwrap();
assert!(expr.contains("chunks[-1]"), "python finish_reason: {expr}");
assert!(
expr.starts_with("(str(") || expr.contains("str(chunks"),
"python finish_reason must wrap in str(): {expr}"
);
}
#[test]
fn accessor_tool_calls_python_uses_list_comprehension() {
let expr = StreamingFieldResolver::accessor("tool_calls", "python", "chunks").unwrap();
assert!(expr.contains("for c in chunks"), "python tool_calls: {expr}");
assert!(expr.contains("tool_calls"), "python tool_calls: {expr}");
}
#[test]
fn accessor_usage_python_uses_last_chunk() {
let expr = StreamingFieldResolver::accessor("usage", "python", "chunks").unwrap();
assert!(
expr.contains("chunks[-1].usage"),
"python usage: expected chunks[-1].usage, got: {expr}"
);
}
#[test]
fn accessor_usage_total_tokens_does_not_route_via_chunks() {
assert!(StreamingFieldResolver::accessor("usage.total_tokens", "python", "chunks").is_none());
}
#[test]
fn accessor_unknown_field_returns_none() {
assert_eq!(
StreamingFieldResolver::accessor("nonexistent_field", "rust", "chunks"),
None
);
}
#[test]
fn is_streaming_virtual_field_recognizes_deep_tool_calls_paths() {
assert!(
is_streaming_virtual_field("tool_calls[0].function.name"),
"tool_calls[0].function.name should be recognized"
);
assert!(
is_streaming_virtual_field("tool_calls[0].id"),
"tool_calls[0].id should be recognized"
);
assert!(
is_streaming_virtual_field("tool_calls[1].function.arguments"),
"tool_calls[1].function.arguments should be recognized"
);
assert!(is_streaming_virtual_field("tool_calls"));
assert!(!is_streaming_virtual_field("tool_calls_extra.name"));
assert!(!is_streaming_virtual_field("nonexistent[0].field"));
}
#[test]
fn deep_tool_calls_function_name_snapshot_rust_kotlin_ts() {
let field = "tool_calls[0].function.name";
let rust = StreamingFieldResolver::accessor(field, "rust", "chunks").unwrap();
assert!(
rust.contains(".nth(0)"),
"rust deep tool_calls: expected .nth(0) iterator index, got: {rust}"
);
assert!(
rust.contains("x.function.as_ref()"),
"rust deep tool_calls: expected Option-aware function access, got: {rust}"
);
assert!(
rust.contains("x.name.as_deref()"),
"rust deep tool_calls: expected Option-aware name leaf, got: {rust}"
);
assert!(
!rust.contains("// skipped"),
"rust deep tool_calls: must not emit skip comment, got: {rust}"
);
let kotlin = StreamingFieldResolver::accessor(field, "kotlin", "chunks").unwrap();
assert!(
kotlin.contains(".first()"),
"kotlin deep tool_calls: expected .first() for index 0, got: {kotlin}"
);
assert!(
kotlin.contains(".function()"),
"kotlin deep tool_calls: expected .function() method call, got: {kotlin}"
);
assert!(
kotlin.contains(".name()"),
"kotlin deep tool_calls: expected .name() method call, got: {kotlin}"
);
let ts = StreamingFieldResolver::accessor(field, "node", "chunks").unwrap();
assert!(
ts.contains("[0]"),
"ts/node deep tool_calls: expected [0] index, got: {ts}"
);
assert!(
ts.contains(".function"),
"ts/node deep tool_calls: expected .function segment, got: {ts}"
);
assert!(
ts.contains(".name"),
"ts/node deep tool_calls: expected .name segment, got: {ts}"
);
}
#[test]
fn deep_tool_calls_id_snapshot_all_langs() {
let field = "tool_calls[0].id";
let rust = StreamingFieldResolver::accessor(field, "rust", "chunks").unwrap();
assert!(rust.contains(".nth(0)"), "rust: {rust}");
assert!(rust.contains("x.id.as_deref()"), "rust: {rust}");
let go = StreamingFieldResolver::accessor(field, "go", "chunks").unwrap();
assert!(go.contains("[0]"), "go: {go}");
assert!(go.contains(".ID"), "go: expected .ID initialism, got: {go}");
let python = StreamingFieldResolver::accessor(field, "python", "chunks").unwrap();
assert!(python.contains("[0]"), "python: {python}");
assert!(python.contains(".id"), "python: {python}");
let php = StreamingFieldResolver::accessor(field, "php", "chunks").unwrap();
assert!(php.contains("[0]"), "php: {php}");
assert!(php.contains("->id"), "php: expected ->id, got: {php}");
let java = StreamingFieldResolver::accessor(field, "java", "chunks").unwrap();
assert!(java.contains(".get(0)"), "java: expected .get(0), got: {java}");
assert!(java.contains(".id()"), "java: expected .id() method call, got: {java}");
let csharp = StreamingFieldResolver::accessor(field, "csharp", "chunks").unwrap();
assert!(csharp.contains("[0]"), "csharp: {csharp}");
assert!(
csharp.contains(".Id"),
"csharp: expected .Id (PascalCase), got: {csharp}"
);
let elixir = StreamingFieldResolver::accessor(field, "elixir", "chunks").unwrap();
assert!(elixir.contains("Enum.at("), "elixir: expected Enum.at(, got: {elixir}");
assert!(elixir.contains(".id"), "elixir: {elixir}");
}
#[test]
fn deep_tool_calls_function_name_snapshot_python_elixir_zig() {
let field = "tool_calls[0].function.name";
let python = StreamingFieldResolver::accessor(field, "python", "chunks").unwrap();
assert!(python.contains("[0]"), "python: {python}");
assert!(python.contains(".function"), "python: {python}");
assert!(python.contains(".name"), "python: {python}");
let elixir = StreamingFieldResolver::accessor(field, "elixir", "chunks").unwrap();
assert!(elixir.contains("Enum.at("), "elixir: {elixir}");
assert!(elixir.contains(".function"), "elixir: {elixir}");
assert!(elixir.contains(".name"), "elixir: {elixir}");
assert!(
StreamingFieldResolver::accessor(field, "zig", "chunks").is_none(),
"zig: expected None for deep tool_calls path"
);
}
#[test]
fn parse_tail_parses_index_then_field_segments() {
let segs = parse_tail("[0].function.name");
assert_eq!(segs.len(), 3, "expected 3 segments, got: {segs:?}");
assert_eq!(segs[0], TailSeg::Index(0));
assert_eq!(segs[1], TailSeg::Field("function".to_string()));
assert_eq!(segs[2], TailSeg::Field("name".to_string()));
}
#[test]
fn parse_tail_parses_simple_index_field() {
let segs = parse_tail("[0].id");
assert_eq!(segs.len(), 2, "expected 2 segments, got: {segs:?}");
assert_eq!(segs[0], TailSeg::Index(0));
assert_eq!(segs[1], TailSeg::Field("id".to_string()));
}
#[test]
fn parse_tail_handles_nonzero_index() {
let segs = parse_tail("[2].function.arguments");
assert_eq!(segs[0], TailSeg::Index(2));
assert_eq!(segs[1], TailSeg::Field("function".to_string()));
assert_eq!(segs[2], TailSeg::Field("arguments".to_string()));
}
#[test]
fn accessor_chunks_length_swift_uses_count() {
let swift = StreamingFieldResolver::accessor("chunks.length", "swift", "chunks").unwrap();
assert_eq!(swift, "chunks.count", "swift chunks.length: {swift}");
}
#[test]
fn accessor_stream_content_swift_uses_swift_closures() {
let expr = StreamingFieldResolver::accessor("stream_content", "swift", "chunks").unwrap();
assert!(
expr.contains("{ c in"),
"swift stream_content must use Swift closure syntax, got: {expr}"
);
assert!(
!expr.contains("=>"),
"swift stream_content must not contain JS arrow `=>`, got: {expr}"
);
assert!(
expr.contains("choices()"),
"swift stream_content must use .choices() method call, got: {expr}"
);
assert!(
expr.contains("delta()"),
"swift stream_content must use .delta() method call, got: {expr}"
);
assert!(
expr.contains("content()"),
"swift stream_content must use .content() method call, got: {expr}"
);
assert!(
expr.contains(".toString()"),
"swift stream_content must convert RustString via .toString(), got: {expr}"
);
assert!(
expr.contains(".joined()"),
"swift stream_content must join with .joined(), got: {expr}"
);
assert!(
!expr.contains(".length"),
"swift stream_content must not use JS .length, got: {expr}"
);
assert!(
!expr.contains(".join("),
"swift stream_content must not use JS .join(, got: {expr}"
);
}
#[test]
fn accessor_stream_complete_swift_uses_swift_syntax() {
let expr = StreamingFieldResolver::accessor("stream_complete", "swift", "chunks").unwrap();
assert!(
expr.contains("isEmpty"),
"swift stream_complete must use .isEmpty, got: {expr}"
);
assert!(
expr.contains(".last!"),
"swift stream_complete must use .last!, got: {expr}"
);
assert!(
expr.contains("choices()"),
"swift stream_complete must use .choices() method call, got: {expr}"
);
assert!(
expr.contains("finish_reason()"),
"swift stream_complete must use .finish_reason(), got: {expr}"
);
assert!(
!expr.contains(".length"),
"swift stream_complete must not use JS .length, got: {expr}"
);
assert!(
!expr.contains("!= null"),
"swift stream_complete must not use JS `!= null`, got: {expr}"
);
}
#[test]
fn accessor_tool_calls_swift_uses_swift_flatmap() {
let expr = StreamingFieldResolver::accessor("tool_calls", "swift", "chunks").unwrap();
assert!(
!expr.contains("=>"),
"swift tool_calls must not contain JS arrow `=>`, got: {expr}"
);
assert!(
expr.contains("flatMap"),
"swift tool_calls must use flatMap, got: {expr}"
);
assert!(
expr.contains("choices()"),
"swift tool_calls must use .choices() method call, got: {expr}"
);
assert!(
expr.contains("delta()"),
"swift tool_calls must use .delta() method call, got: {expr}"
);
assert!(
expr.contains("tool_calls()"),
"swift tool_calls must use .tool_calls() method call, got: {expr}"
);
}
#[test]
fn accessor_finish_reason_swift_uses_swift_syntax() {
let expr = StreamingFieldResolver::accessor("finish_reason", "swift", "chunks").unwrap();
assert!(
expr.contains("isEmpty"),
"swift finish_reason must use .isEmpty, got: {expr}"
);
assert!(
expr.contains(".last!"),
"swift finish_reason must use .last!, got: {expr}"
);
assert!(
expr.contains("finish_reason()"),
"swift finish_reason must use .finish_reason() method call, got: {expr}"
);
assert!(
expr.contains(".toString()"),
"swift finish_reason must convert RustString via .toString(), got: {expr}"
);
assert!(
!expr.contains("undefined"),
"swift finish_reason must not use JS `undefined`, got: {expr}"
);
assert!(
!expr.contains(".length"),
"swift finish_reason must not use JS .length, got: {expr}"
);
}
#[test]
fn accessor_usage_swift_uses_swift_syntax() {
let expr = StreamingFieldResolver::accessor("usage", "swift", "chunks").unwrap();
assert!(expr.contains("isEmpty"), "swift usage must use .isEmpty, got: {expr}");
assert!(expr.contains(".last!"), "swift usage must use .last!, got: {expr}");
assert!(
expr.contains("usage()"),
"swift usage must use .usage() method call, got: {expr}"
);
assert!(
!expr.contains("undefined"),
"swift usage must not use JS `undefined`, got: {expr}"
);
assert!(
!expr.contains(".length"),
"swift usage must not use JS .length, got: {expr}"
);
}
}