TypeScript 97.4%
SQL 1%
JavaScript 0.9%
CSS 0.6%
1// Probe 09: Anthropic thinking (reasoning.max_tokens) + tools, streamed, then round 2 passing reasoning_details back (and once without);2// stream without any usage flag; /generation lookup with a longer delay; /models vs /models/user diff.3import { readFileSync } from "node:fs";4import { raw, rawSSE, save, short, OUT } from "./lib.ts";5import { ANTHROPIC, GOOGLE, OPENAI } from "./models.ts";67const results: any = {};8const tools = [9 { type: "function", function: { name: "get_weather", description: "Get the current weather for a city", parameters: { type: "object", properties: { city: { type: "string" } }, required: ["city"] } } },10];1112async function streamCollect(body: any) {13 const s = await rawSSE("/chat/completions", { ...body, stream: true });14 const acc: Record<number, any> = {};15 const details: any[] = [];16 let reasoning = "";17 let content = "";18 let finish: string | undefined;19 let usage: any;20 const detailChunks: any[] = [];21 for (const e of s.events) {22 if (e.data === "[DONE]") continue;23 const c = e.data?.choices?.[0];24 const d = c?.delta ?? {};25 if (d.reasoning) reasoning += d.reasoning;26 if (d.content) content += d.content;27 if (d.reasoning_details) {28 details.push(...d.reasoning_details);29 if (detailChunks.length < 3) detailChunks.push(d.reasoning_details);30 }31 for (const tc of d.tool_calls ?? []) {32 const i = tc.index ?? 0;33 acc[i] ??= { id: tc.id, type: "function", function: { name: "", arguments: "" } };34 if (tc.id) acc[i].id = tc.id;35 if (tc.function?.name) acc[i].function.name += tc.function.name;36 if (tc.function?.arguments) acc[i].function.arguments += tc.function.arguments;37 }38 if (c?.finish_reason) finish = c.finish_reason;39 if (e.data?.usage) usage = e.data.usage;40 if (e.data?.error) results.streamError = e.data;41 }42 return { status: s.status, error: (s as any).error, toolCalls: Object.values(acc), details, detailChunks, reasoning, content, finish, usage, events: s.events.length };43}4445for (const model of [ANTHROPIC, GOOGLE]) {46 const messages: any[] = [{ role: "user", content: "What's the weather in Montreal right now? Use the tool." }];47 const r1 = await streamCollect({ model, messages, tools, max_tokens: 1500, reasoning: { max_tokens: 1024 } });48 console.log(`\n[${model}] round1 ${r1.status} finish=${r1.finish} calls=${JSON.stringify(r1.toolCalls)} rt=${r1.usage?.completion_tokens_details?.reasoning_tokens} reasoning=${short(r1.reasoning, 80)}`);49 console.log(" detail types:", [...new Set(r1.details.map((d) => d.type))], "count:", r1.details.length);50 console.log(" first detail chunks:", short(r1.detailChunks.map((c: any[]) => c.map((d) => ({ ...d, text: d.text?.slice(0, 30), signature: d.signature?.slice(0, 16), data: d.data?.slice(0, 16) }))), 600));51 if (r1.error) console.log(" ERR", short(r1.error));52 results[`${model}_round1`] = r1;53 if (!r1.toolCalls.length) continue;5455 // Merge reasoning_details by index/type (docs: pass back unmodified; streaming yields fragments)56 const merged: any[] = [];57 for (const d of r1.details) {58 const last = merged[merged.length - 1];59 if (last && last.type === d.type && last.index === d.index) {60 if (d.text) last.text = (last.text ?? "") + d.text;61 if (d.summary) last.summary = (last.summary ?? "") + d.summary;62 if (d.data) last.data = (last.data ?? "") + d.data;63 if (d.signature) last.signature = d.signature;64 if (d.id) last.id = d.id;65 } else merged.push({ ...d });66 }67 results[`${model}_merged_details`] = merged.map((d) => ({ ...d, text: d.text?.slice(0, 60), signature: d.signature?.slice(0, 24), data: d.data?.slice(0, 24) }));68 console.log(" merged details:", short(results[`${model}_merged_details`], 500));6970 const toolMsgs = r1.toolCalls.map((tc: any) => ({ role: "tool", tool_call_id: tc.id, content: JSON.stringify({ city: "Montreal", temp_c: 21, sky: "sunny" }) }));71 // (a) with reasoning_details passed back72 const withDetails = [...messages, { role: "assistant", content: r1.content || null, tool_calls: r1.toolCalls, reasoning_details: merged }, ...toolMsgs];73 const a = await raw("/chat/completions", { method: "POST", body: JSON.stringify({ model, messages: withDetails, tools, max_tokens: 1500, reasoning: { max_tokens: 1024 } }) });74 const ab: any = a.body;75 results[`${model}_round2_with_details`] = { status: a.status, finish: ab?.choices?.[0]?.finish_reason, content: ab?.choices?.[0]?.message?.content, usage: ab?.usage, error: ab?.error };76 console.log(` round2 WITH details: ${a.status} ${short(ab?.choices?.[0]?.message?.content, 100)} ${ab?.error ? "ERR=" + short(ab.error, 300) : ""}`);77 // (b) without reasoning_details78 const without = [...messages, { role: "assistant", content: r1.content || null, tool_calls: r1.toolCalls }, ...toolMsgs];79 const b = await raw("/chat/completions", { method: "POST", body: JSON.stringify({ model, messages: without, tools, max_tokens: 1500, reasoning: { max_tokens: 1024 } }) });80 const bb: any = b.body;81 results[`${model}_round2_without_details`] = { status: b.status, finish: bb?.choices?.[0]?.finish_reason, content: bb?.choices?.[0]?.message?.content, usage: bb?.usage, error: bb?.error };82 console.log(` round2 WITHOUT details: ${b.status} ${short(bb?.choices?.[0]?.message?.content, 100)} ${bb?.error ? "ERR=" + short(bb.error, 300) : ""}`);83}8485// Stream with no usage flag at all86{87 const s = await rawSSE("/chat/completions", { model: OPENAI, messages: [{ role: "user", content: "hi" }], max_tokens: 5, stream: true, reasoning: { effort: "none" } });88 const usageChunk = s.events.find((e) => e.data?.usage)?.data;89 results.stream_no_usage_flag = { status: s.status, hasUsage: !!usageChunk, usage: usageChunk?.usage };90 console.log(`\n[stream without usage flag] ${s.status} usage present=${!!usageChunk} ${JSON.stringify(usageChunk?.usage)}`);91 // and with stream_options.include_usage (OpenAI SDK style)92 const s2 = await rawSSE("/chat/completions", { model: OPENAI, messages: [{ role: "user", content: "hi" }], max_tokens: 5, stream: true, stream_options: { include_usage: true }, reasoning: { effort: "none" } });93 results.stream_options_include_usage = { status: s2.status, hasUsage: s2.events.some((e) => e.data?.usage), error: (s2 as any).error };94 console.log(`[stream_options.include_usage] ${s2.status} usage present=${results.stream_options_include_usage.hasUsage} ${(s2 as any).error ? JSON.stringify((s2 as any).error) : ""}`);95}9697// /generation lookup with a longer delay98{99 const chat = JSON.parse(readFileSync(`${OUT}01-chat.json`, "utf8"));100 const id = chat[OPENAI]?.body?.id;101 const g = await raw(`/generation?id=${id}`);102 results.generation = { id, status: g.status, body: g.body };103 console.log(`\n[generation ${id}] ${g.status} ${short(g.body, 900)}`);104}105106// /models vs /models/user107{108 const all: any[] = JSON.parse(readFileSync(`${OUT}models.json`, "utf8")).data;109 const user = await raw("/models/user");110 const userIds = new Set(((user.body as any)?.data ?? []).map((m: any) => m.id));111 const missing = all.filter((m) => !userIds.has(m.id)).map((m) => m.id);112 results.models_user_diff = { allCount: all.length, userCount: userIds.size, missingFromUser: missing };113 console.log(`\n[/models/user] ${user.status} user=${userIds.size} all=${all.length} missing=${JSON.stringify(missing)}`);114}115save("09-reasoning-roundtrip.json", results);116