pi-meta-oauth 0.4.3 → 0.4.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,186 @@
1
+ import type { Usage } from "@earendil-works/pi-ai";
2
+ import { META_API_BASE_URL, metaFallbackCost } from "../meta.ts";
3
+
4
+ function record(value: unknown): Record<string, unknown> | undefined {
5
+ return value !== null && typeof value === "object"
6
+ ? (value as Record<string, unknown>)
7
+ : undefined;
8
+ }
9
+
10
+ function tokenCount(...values: unknown[]): number | undefined {
11
+ for (const value of values) {
12
+ const numeric =
13
+ typeof value === "number"
14
+ ? value
15
+ : typeof value === "string" && value.trim()
16
+ ? Number(value)
17
+ : Number.NaN;
18
+ if (Number.isFinite(numeric)) return Math.max(0, Math.trunc(numeric));
19
+ }
20
+ return undefined;
21
+ }
22
+
23
+ function responsesErrorDetail(json: unknown, text: string, statusText: string): string {
24
+ const body = record(json);
25
+ if (body?.error !== undefined) {
26
+ return JSON.stringify(body.error).slice(0, 1000);
27
+ }
28
+ if (typeof body?.message === "string") {
29
+ return body.message.slice(0, 1000);
30
+ }
31
+ return text.slice(0, 1000) || statusText;
32
+ }
33
+
34
+ /** Convert a Meta Responses API usage block into Pi's nested-tool usage shape. */
35
+ export function extractMetaResponseUsage(
36
+ response: unknown,
37
+ modelId = "muse-spark-1.2",
38
+ ): Usage | undefined {
39
+ const usage = record(record(response)?.usage);
40
+ if (!usage) return undefined;
41
+
42
+ const inputDetails = record(
43
+ usage.input_tokens_details ?? usage.inputTokensDetails,
44
+ );
45
+ const outputDetails = record(
46
+ usage.output_tokens_details ?? usage.outputTokensDetails,
47
+ );
48
+ const inputTotal = tokenCount(
49
+ usage.input_tokens,
50
+ usage.inputTokens,
51
+ usage.prompt_tokens,
52
+ usage.promptTokens,
53
+ usage.input,
54
+ );
55
+ const output = tokenCount(
56
+ usage.output_tokens,
57
+ usage.outputTokens,
58
+ usage.completion_tokens,
59
+ usage.completionTokens,
60
+ usage.output,
61
+ );
62
+ if (inputTotal === undefined && output === undefined) return undefined;
63
+
64
+ const reportedInput = inputTotal ?? 0;
65
+ const cacheRead = Math.min(
66
+ reportedInput,
67
+ tokenCount(inputDetails?.cached_tokens, inputDetails?.cachedTokens) ?? 0,
68
+ );
69
+ const cacheWrite = Math.min(
70
+ reportedInput - cacheRead,
71
+ tokenCount(
72
+ inputDetails?.cache_write_tokens,
73
+ inputDetails?.cacheWriteTokens,
74
+ ) ?? 0,
75
+ );
76
+ const uncachedInput = Math.max(0, reportedInput - cacheRead - cacheWrite);
77
+ const outputTokens = output ?? 0;
78
+ const reasoning = Math.min(
79
+ outputTokens,
80
+ tokenCount(
81
+ outputDetails?.reasoning_tokens,
82
+ outputDetails?.reasoningTokens,
83
+ ) ?? 0,
84
+ );
85
+ const totalTokens =
86
+ tokenCount(usage.total_tokens, usage.totalTokens) ??
87
+ uncachedInput + cacheRead + cacheWrite + outputTokens;
88
+ const rates = metaFallbackCost(modelId);
89
+ const cost = {
90
+ input: (uncachedInput * (rates?.input ?? 0)) / 1_000_000,
91
+ output: (outputTokens * (rates?.output ?? 0)) / 1_000_000,
92
+ cacheRead: (cacheRead * (rates?.cacheRead ?? 0)) / 1_000_000,
93
+ cacheWrite: (cacheWrite * (rates?.cacheWrite ?? 0)) / 1_000_000,
94
+ total: 0,
95
+ };
96
+ cost.total = cost.input + cost.output + cost.cacheRead + cost.cacheWrite;
97
+
98
+ return {
99
+ input: uncachedInput,
100
+ output: outputTokens,
101
+ cacheRead,
102
+ cacheWrite,
103
+ reasoning,
104
+ totalTokens,
105
+ cost,
106
+ };
107
+ }
108
+
109
+ export function formatMetaResponseUsage(usage: Usage): string {
110
+ const parts = [`${usage.input.toLocaleString()} input`];
111
+ if (usage.cacheRead)
112
+ parts.push(`${usage.cacheRead.toLocaleString()} cache read`);
113
+ if (usage.cacheWrite)
114
+ parts.push(`${usage.cacheWrite.toLocaleString()} cache write`);
115
+ parts.push(`${usage.output.toLocaleString()} output`);
116
+ if (usage.reasoning)
117
+ parts.push(`${usage.reasoning.toLocaleString()} reasoning`);
118
+ return `Video token usage: ${usage.totalTokens.toLocaleString()} total (${parts.join(", ")})`;
119
+ }
120
+
121
+ export function extractResponsesText(json: unknown): string {
122
+ if (!json || typeof json !== "object") return "";
123
+ const j = json as Record<string, unknown>;
124
+ if (typeof j.output_text === "string" && j.output_text.trim())
125
+ return j.output_text;
126
+ const output = j.output as unknown;
127
+ if (Array.isArray(output)) {
128
+ const texts: string[] = [];
129
+ for (const item of output as Record<string, unknown>[]) {
130
+ if (!item || typeof item !== "object" || item.type !== "message")
131
+ continue;
132
+ if (typeof item.content === "string" && item.content.trim())
133
+ texts.push(item.content);
134
+ if (Array.isArray(item.content)) {
135
+ for (const c of item.content as Record<string, unknown>[]) {
136
+ if (
137
+ (c.type === "output_text" || c.type === "text") &&
138
+ typeof c.text === "string" &&
139
+ c.text.trim()
140
+ )
141
+ texts.push(c.text);
142
+ if (
143
+ c.type === "refusal" &&
144
+ typeof c.refusal === "string" &&
145
+ c.refusal.trim()
146
+ )
147
+ texts.push(c.refusal);
148
+ }
149
+ }
150
+ }
151
+ if (texts.length) return texts.join("\n\n");
152
+ }
153
+ if (typeof j.text === "string" && j.text.trim()) return j.text;
154
+ return "";
155
+ }
156
+
157
+ export async function callMetaResponses(
158
+ apiKey: string,
159
+ payload: Record<string, unknown>,
160
+ signal?: AbortSignal,
161
+ ): Promise<{ text: string; raw: unknown }> {
162
+ payload.store = false;
163
+ const res = await fetch(`${META_API_BASE_URL}/responses`, {
164
+ method: "POST",
165
+ headers: {
166
+ Authorization: `Bearer ${apiKey}`,
167
+ "Content-Type": "application/json",
168
+ "x-api-version": "1.0.0",
169
+ },
170
+ body: JSON.stringify(payload),
171
+ signal,
172
+ });
173
+ const text = await res.text();
174
+ let json: unknown;
175
+ try {
176
+ json = text ? (JSON.parse(text) as unknown) : {};
177
+ } catch {
178
+ json = text;
179
+ }
180
+ if (!res.ok) {
181
+ throw new Error(
182
+ `Meta Responses failed (HTTP ${res.status}): ${responsesErrorDetail(json, text, res.statusText)}`,
183
+ );
184
+ }
185
+ return { text: extractResponsesText(json), raw: json };
186
+ }
@@ -101,9 +101,13 @@ export async function prepareMediaOutput(options: {
101
101
  const savedPath = truncated ? await saveFullOutput(identity, text) : undefined;
102
102
  const notes: string[] = [];
103
103
  if (truncated) {
104
+ const continuation =
105
+ lines <= 1
106
+ ? `full content saved to: ${savedPath}. Open that file directly; it is a single line so read offset/limit cannot page through it.`
107
+ : `full content saved to: ${savedPath} (${lines} lines). Use the read tool with offset/limit to continue (offset is a 1-based line number).`;
104
108
  notes.push(
105
109
  savedPath
106
- ? `content truncated at ${slice.length} of ${text.length} characters; full content saved to: ${savedPath} (${lines} lines). Use the read tool with offset/limit to continue (offset is a 1-based line number).`
110
+ ? `content truncated at ${slice.length} of ${text.length} characters; ${continuation}`
107
111
  : `content truncated at ${slice.length} of ${text.length} characters; the full content could not be saved, so continuation is unavailable.`,
108
112
  );
109
113
  }