Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 15 additions & 1 deletion src/chrome/src/agent/agent.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { AGENT_TOOLS, AGENT_TOOL_NAMES, RESERVED_AGENT_TOOL_NAMES, getToolsForMo
import { validateToolArguments } from './tool-arguments.js';
import { isSessionQuotaError, serializeConversationForSession, SESSION_CONVERSATION_BUDGET_BYTES, SESSION_CONVERSATION_RETRY_BUDGET_BYTES } from './conversation-persistence.js';
import { formatErrorMessage } from '../error-format.js';
import { aggregateMessageCompletion } from '../message-info.js';
import { handleDoneJson } from './cloud-output.js';
import { applyReadPageWindow, fitReadPageWindowResult, isReadPageWindowResult } from './read-page-window.js';
import { STANDARD_TOOL_RESULT_CHARS, createReadCompletenessState, isCommunicationThreadContext, normalizeReadScope, readCompletenessBlock, readCompletenessLimitation, readCompletenessMadeProgress, readWindowLimits, recordReadCompleteness, requirePlannerReadCompleteness, requiresCompleteThreadRead } from './read-completeness.js';
Expand Down Expand Up @@ -24334,6 +24335,7 @@ Rules: no prose intro, no conclusion, no "this screenshot shows...", no layout d

let runId = null;
let finalResponse = '';
let messageCompletion = null;
let _traceStatus = 'done'; // updated on early exits
let askStreamingTraceWrite = Promise.resolve();
let shouldOrderInteractiveAskTrace = false;
Expand Down Expand Up @@ -24515,7 +24517,7 @@ Rules: no prose intro, no conclusion, no "this screenshot shows...", no layout d
);
};

const chatMainTurn = async (chatMessages, chatOptions, requestContext) => {
const chatMainTurnRaw = async (chatMessages, chatOptions, requestContext) => {
const decision = this._interactiveAskStreamingDecision(
provider,
mode,
Expand Down Expand Up @@ -24610,6 +24612,18 @@ Rules: no prose intro, no conclusion, no "this screenshot shows...", no layout d
}
};

const chatMainTurn = async (chatMessages, chatOptions, requestContext) => {
const startedAt = Date.now();
const result = await chatMainTurnRaw(chatMessages, chatOptions, requestContext);

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in aafa249. The collector in _chatStreamWithCostAllowance now preserves the terminal reason from the provider done chunk (and its raw payload when present) and passes it into the result, so aggregateMessageCompletion can read result.finishReason instead of always receiving an empty reason on the primary streaming Ask path. The streaming providers that actually observe a terminal reason (OpenAI Chat Completions choice.finish_reason, Anthropic message_delta.delta.stop_reason, llama.cpp / Azure choice.finish_reason, Bedrock stopReason) now attach it to their done chunk. The Responses API has no per-generation stop reason, and the aggregation test already treats lifecycle status as not-a-reason, so no value is fabricated there. Verified: node test/run.js 1755 passed.

messageCompletion = aggregateMessageCompletion(
messageCompletion,
result,
Date.now() - startedAt,
);
onUpdate('message_info', messageCompletion);
return result;
};

if (!runId) {
runId = await this._startTraceRun(
tabId, userMessage, mode, provider, null, runOptions,
Expand Down
117 changes: 117 additions & 0 deletions src/chrome/src/message-info.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
function formatSentTime(createdAt, locale) {
const value = Number(createdAt);
if (!Number.isFinite(value) || value <= 0) return '';
try {
return new Intl.DateTimeFormat(locale || undefined, {
year: 'numeric',
month: '2-digit',
day: '2-digit',
hour: '2-digit',
minute: '2-digit',
hour12: false,
timeZone: 'UTC',
timeZoneName: 'short',
}).format(new Date(value));
} catch {
return new Date(value).toISOString().replace('T', ' ').slice(0, 16) + ' UTC';
}
}

function firstPositiveInteger(...values) {
for (const value of values) {
const number = Number(value);
if (Number.isFinite(number) && number > 0) return Math.floor(number);
}
return 0;
}

function finishReason(result) {
const raw = result?.raw || {};
return String(
result?.finishReason
?? raw?.choices?.[0]?.finish_reason
?? raw?.stop_reason
?? raw?.stopReason
?? '',
).replace(/[\u0000-\u001f\u007f]/g, '').trim().slice(0, 80);
}

function formatNumber(value, locale, maximumFractionDigits = 0) {
try {
return new Intl.NumberFormat(locale || undefined, {
maximumFractionDigits,
}).format(value);
} catch {
return Number(value).toFixed(maximumFractionDigits).replace(/\.0+$/, '');
}
}

export function aggregateMessageCompletion(current, result, durationMs) {
const previous = current || {};
const usage = result?.usage || {};
const inputTokens = firstPositiveInteger(
usage.prompt_tokens,
usage.input_tokens,
usage.promptTokens,
usage.inputTokens,
);
const outputTokens = firstPositiveInteger(
usage.completion_tokens,
usage.output_tokens,
usage.completionTokens,
usage.outputTokens,
);
const reportedTotal = firstPositiveInteger(usage.total_tokens, usage.totalTokens);
const elapsed = Number(durationMs);
return {
inputTokens: firstPositiveInteger(previous.inputTokens) + inputTokens,
outputTokens: firstPositiveInteger(previous.outputTokens) + outputTokens,
totalTokens: firstPositiveInteger(previous.totalTokens) + (reportedTotal || inputTokens + outputTokens),
durationMs: firstPositiveInteger(previous.durationMs) + (Number.isFinite(elapsed) && elapsed > 0 ? Math.round(elapsed) : 0),
finishReason: finishReason(result) || String(previous.finishReason || '').slice(0, 80),
};
}

export function buildMessageInfoPills({ createdAt, completion = {}, verbose = false, locale } = {}) {
const time = formatSentTime(createdAt, locale);
if (!time) return [];
const pills = [{
kind: 'sent',
key: 'sp.message_info.sent',
params: { time },
}];
if (!verbose) return pills;
const outputTokens = firstPositiveInteger(completion.outputTokens);
const displayedTokens = outputTokens || firstPositiveInteger(completion.totalTokens);
const durationMs = firstPositiveInteger(completion.durationMs);
if (outputTokens && durationMs) {
pills.push({
kind: 'speed',
key: 'sp.message_info.speed',
params: { rate: formatNumber((outputTokens * 1000) / durationMs, locale, 2) },
});
}
if (displayedTokens) {
pills.push({
kind: 'tokens',
key: 'sp.message_info.tokens',
params: { count: formatNumber(displayedTokens, locale) },
});
}
if (durationMs) {
pills.push({
kind: 'duration',
key: 'sp.message_info.duration',
params: { seconds: formatNumber(durationMs / 1000, locale, 2) },
});
}
const reason = String(completion.finishReason || '').trim().slice(0, 80);
if (reason) {
pills.push({
kind: 'finish',
key: 'sp.message_info.finish',
params: { reason },
});
}
return pills;
}
6 changes: 6 additions & 0 deletions src/chrome/src/ui/locales/ar.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,12 @@ import chromeWebStoreLocale from './chrome-web-store.mjs';
import { getApocalypseModeCopy } from './apocalypse-copy.mjs';

export default {
'sp.message_info.sent': '(sent {time})',
'sp.message_info.speed': '{rate} tok/sec',
'sp.message_info.tokens': '{count} tokens',
'sp.message_info.duration': '{seconds}s',
'sp.message_info.finish': 'Stop reason: {reason}',
'sp.message_info.hint': 'Click to show message info',
...chromeWebStoreLocale,
'sp.streaming.fallback': 'انقطع بث الاستجابة؛ تتم إعادة محاولة دور Ask هذا بدون بث.',
'sp.providers.no_setup_group': 'لا يتطلب إعدادًا',
Expand Down
6 changes: 6 additions & 0 deletions src/chrome/src/ui/locales/bn.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,12 @@
import { getApocalypseModeCopy } from './apocalypse-copy.mjs';

export default {
'sp.message_info.sent': '(sent {time})',
'sp.message_info.speed': '{rate} tok/sec',
'sp.message_info.tokens': '{count} tokens',
'sp.message_info.duration': '{seconds}s',
'sp.message_info.finish': 'Stop reason: {reason}',
'sp.message_info.hint': 'Click to show message info',
'sp.streaming.fallback': 'প্রতিক্রিয়া স্ট্রিম বাধাগ্রস্ত হয়েছে; স্ট্রিমিং ছাড়া এই Ask পালাটি আবার চেষ্টা করা হচ্ছে।',
'sp.providers.no_setup_group': "কোন সেটআপ প্রয়োজন",
'sp.providers.no_setup': "কোনো সেটআপ নেই",
Expand Down
6 changes: 6 additions & 0 deletions src/chrome/src/ui/locales/de.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,12 @@ import chromeWebStoreLocale from './chrome-web-store.mjs';
import { getApocalypseModeCopy } from './apocalypse-copy.mjs';

export default {
'sp.message_info.sent': '(sent {time})',
'sp.message_info.speed': '{rate} tok/sec',
'sp.message_info.tokens': '{count} tokens',
'sp.message_info.duration': '{seconds}s',
'sp.message_info.finish': 'Stop reason: {reason}',
'sp.message_info.hint': 'Click to show message info',
...chromeWebStoreLocale,
'sp.streaming.fallback': 'Der Antwortstream wurde unterbrochen; dieser Ask-Durchgang wird ohne Streaming erneut versucht.',
'sp.providers.no_setup_group': 'Keine Einrichtung erforderlich',
Expand Down
6 changes: 6 additions & 0 deletions src/chrome/src/ui/locales/en.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,12 @@
import apocalypseModeCopy from './apocalypse-copy.mjs';

export default {
'sp.message_info.sent': '(sent {time})',
'sp.message_info.speed': '{rate} tok/sec',
'sp.message_info.tokens': '{count} tokens',
'sp.message_info.duration': '{seconds}s',
'sp.message_info.finish': 'Stop reason: {reason}',
'sp.message_info.hint': 'Click to show message info',
'sp.streaming.fallback': 'Response streaming was interrupted; retrying this Ask turn without streaming.',
'sp.providers.no_setup_group': 'No setup required',
'sp.providers.no_setup': 'No setup',
Expand Down
6 changes: 6 additions & 0 deletions src/chrome/src/ui/locales/es.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,12 @@ import chromeWebStoreLocale from './chrome-web-store.mjs';
import { getApocalypseModeCopy } from './apocalypse-copy.mjs';

export default {
'sp.message_info.sent': '(sent {time})',
'sp.message_info.speed': '{rate} tok/sec',
'sp.message_info.tokens': '{count} tokens',
'sp.message_info.duration': '{seconds}s',
'sp.message_info.finish': 'Stop reason: {reason}',
'sp.message_info.hint': 'Click to show message info',
...chromeWebStoreLocale,
'sp.streaming.fallback': 'Se interrumpió la transmisión de la respuesta; reintentando este turno de Ask sin transmisión.',
'sp.providers.no_setup_group': 'Sin configuración',
Expand Down
6 changes: 6 additions & 0 deletions src/chrome/src/ui/locales/fa.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,12 @@
import { getApocalypseModeCopy } from './apocalypse-copy.mjs';

export default {
'sp.message_info.sent': '(sent {time})',
'sp.message_info.speed': '{rate} tok/sec',
'sp.message_info.tokens': '{count} tokens',
'sp.message_info.duration': '{seconds}s',
'sp.message_info.finish': 'Stop reason: {reason}',
'sp.message_info.hint': 'Click to show message info',
'sp.streaming.fallback': 'جریان پاسخ قطع شد؛ این نوبت Ask بدون پخش جریانی دوباره امتحان می‌شود.',
'sp.providers.no_setup_group': "بدون نیاز به راه اندازی",
'sp.providers.no_setup': "بدون راه اندازی",
Expand Down
6 changes: 6 additions & 0 deletions src/chrome/src/ui/locales/fr.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,12 @@ import chromeWebStoreLocale from './chrome-web-store.mjs';
import { getApocalypseModeCopy } from './apocalypse-copy.mjs';

export default {
'sp.message_info.sent': '(sent {time})',
'sp.message_info.speed': '{rate} tok/sec',
'sp.message_info.tokens': '{count} tokens',
'sp.message_info.duration': '{seconds}s',
'sp.message_info.finish': 'Stop reason: {reason}',
'sp.message_info.hint': 'Click to show message info',
...chromeWebStoreLocale,
'sp.streaming.fallback': 'Le flux de réponse a été interrompu ; nouvelle tentative de ce tour Ask sans streaming.',
'sp.providers.no_setup_group': 'Sans configuration',
Expand Down
6 changes: 6 additions & 0 deletions src/chrome/src/ui/locales/he.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,12 @@ import chromeWebStoreLocale from './chrome-web-store.mjs';
import { getApocalypseModeCopy } from './apocalypse-copy.mjs';

export default {
'sp.message_info.sent': '(sent {time})',
'sp.message_info.speed': '{rate} tok/sec',
'sp.message_info.tokens': '{count} tokens',
'sp.message_info.duration': '{seconds}s',
'sp.message_info.finish': 'Stop reason: {reason}',
'sp.message_info.hint': 'Click to show message info',
...chromeWebStoreLocale,
'sp.streaming.fallback': 'הזרמת התשובה נקטעה; מתבצע ניסיון חוזר לתור Ask הזה ללא הזרמה.',
'sp.providers.no_setup_group': 'ללא הגדרה',
Expand Down
6 changes: 6 additions & 0 deletions src/chrome/src/ui/locales/hi.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,12 @@
import { getApocalypseModeCopy } from './apocalypse-copy.mjs';

export default {
'sp.message_info.sent': '(sent {time})',
'sp.message_info.speed': '{rate} tok/sec',
'sp.message_info.tokens': '{count} tokens',
'sp.message_info.duration': '{seconds}s',
'sp.message_info.finish': 'Stop reason: {reason}',
'sp.message_info.hint': 'Click to show message info',
'sp.streaming.fallback': 'प्रतिक्रिया स्ट्रीम बाधित हुई; इस Ask टर्न को बिना स्ट्रीमिंग के फिर से आज़माया जा रहा है।',
'sp.providers.no_setup_group': "किसी सेटअप की आवश्यकता नहीं है",
'sp.providers.no_setup': "कोई सेटअप नहीं",
Expand Down
6 changes: 6 additions & 0 deletions src/chrome/src/ui/locales/id.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,12 @@ import chromeWebStoreLocale from './chrome-web-store.mjs';
import { getApocalypseModeCopy } from './apocalypse-copy.mjs';

export default {
'sp.message_info.sent': '(sent {time})',
'sp.message_info.speed': '{rate} tok/sec',
'sp.message_info.tokens': '{count} tokens',
'sp.message_info.duration': '{seconds}s',
'sp.message_info.finish': 'Stop reason: {reason}',
'sp.message_info.hint': 'Click to show message info',
...chromeWebStoreLocale,
'sp.streaming.fallback': 'Streaming respons terputus; mencoba kembali giliran Ask ini tanpa streaming.',
'sp.providers.no_setup_group': 'Tanpa penyiapan',
Expand Down
6 changes: 6 additions & 0 deletions src/chrome/src/ui/locales/ja.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,12 @@ import chromeWebStoreLocale from './chrome-web-store.mjs';
import { getApocalypseModeCopy } from './apocalypse-copy.mjs';

export default {
'sp.message_info.sent': '(sent {time})',
'sp.message_info.speed': '{rate} tok/sec',
'sp.message_info.tokens': '{count} tokens',
'sp.message_info.duration': '{seconds}s',
'sp.message_info.finish': 'Stop reason: {reason}',
'sp.message_info.hint': 'Click to show message info',
...chromeWebStoreLocale,
'sp.streaming.fallback': '応答ストリームが中断されました。この Ask ターンをストリーミングなしで再試行します。',
'sp.providers.no_setup_group': '設定不要',
Expand Down
6 changes: 6 additions & 0 deletions src/chrome/src/ui/locales/ko.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,12 @@ import chromeWebStoreLocale from './chrome-web-store.mjs';
import { getApocalypseModeCopy } from './apocalypse-copy.mjs';

export default {
'sp.message_info.sent': '(sent {time})',
'sp.message_info.speed': '{rate} tok/sec',
'sp.message_info.tokens': '{count} tokens',
'sp.message_info.duration': '{seconds}s',
'sp.message_info.finish': 'Stop reason: {reason}',
'sp.message_info.hint': 'Click to show message info',
...chromeWebStoreLocale,
'sp.streaming.fallback': '응답 스트리밍이 중단되었습니다. 이 Ask 요청을 스트리밍 없이 다시 시도합니다.',
'sp.providers.no_setup_group': '설정 필요 없음',
Expand Down
6 changes: 6 additions & 0 deletions src/chrome/src/ui/locales/ms.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,12 @@ import chromeWebStoreLocale from './chrome-web-store.mjs';
import { getApocalypseModeCopy } from './apocalypse-copy.mjs';

export default {
'sp.message_info.sent': '(sent {time})',
'sp.message_info.speed': '{rate} tok/sec',
'sp.message_info.tokens': '{count} tokens',
'sp.message_info.duration': '{seconds}s',
'sp.message_info.finish': 'Stop reason: {reason}',
'sp.message_info.hint': 'Click to show message info',
...chromeWebStoreLocale,
'sp.streaming.fallback': 'Penstriman respons terganggu; mencuba semula giliran Ask ini tanpa penstriman.',
'sp.providers.no_setup_group': 'Tanpa persediaan',
Expand Down
6 changes: 6 additions & 0 deletions src/chrome/src/ui/locales/nl.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,12 @@ import chromeWebStoreLocale from './chrome-web-store.mjs';
import { getApocalypseModeCopy } from './apocalypse-copy.mjs';

export default {
'sp.message_info.sent': '(sent {time})',
'sp.message_info.speed': '{rate} tok/sec',
'sp.message_info.tokens': '{count} tokens',
'sp.message_info.duration': '{seconds}s',
'sp.message_info.finish': 'Stop reason: {reason}',
'sp.message_info.hint': 'Click to show message info',
...chromeWebStoreLocale,
'sp.streaming.fallback': 'De antwoordstream is onderbroken; deze Ask-beurt wordt opnieuw geprobeerd zonder streaming.',
'sp.providers.no_setup_group': 'Geen configuratie nodig',
Expand Down
6 changes: 6 additions & 0 deletions src/chrome/src/ui/locales/pl.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,12 @@ import chromeWebStoreLocale from './chrome-web-store.mjs';
import { getApocalypseModeCopy } from './apocalypse-copy.mjs';

export default {
'sp.message_info.sent': '(sent {time})',
'sp.message_info.speed': '{rate} tok/sec',
'sp.message_info.tokens': '{count} tokens',
'sp.message_info.duration': '{seconds}s',
'sp.message_info.finish': 'Stop reason: {reason}',
'sp.message_info.hint': 'Click to show message info',
...chromeWebStoreLocale,
'sp.streaming.fallback': 'Strumieniowanie odpowiedzi zostało przerwane; ponawiam tę turę Ask bez strumieniowania.',
'sp.providers.no_setup_group': 'Bez konfiguracji',
Expand Down
6 changes: 6 additions & 0 deletions src/chrome/src/ui/locales/pt.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,12 @@
import { getApocalypseModeCopy } from './apocalypse-copy.mjs';

export default {
'sp.message_info.sent': '(sent {time})',
'sp.message_info.speed': '{rate} tok/sec',
'sp.message_info.tokens': '{count} tokens',
'sp.message_info.duration': '{seconds}s',
'sp.message_info.finish': 'Stop reason: {reason}',
'sp.message_info.hint': 'Click to show message info',
'sp.streaming.fallback': 'A transmissão da resposta foi interrompida; tentando novamente esta interação Ask sem transmissão.',
'sp.providers.no_setup_group': "Nenhuma configuração necessária",
'sp.providers.no_setup': "Sem configuração",
Expand Down
6 changes: 6 additions & 0 deletions src/chrome/src/ui/locales/ru.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,12 @@ import chromeWebStoreLocale from './chrome-web-store.mjs';
import { getApocalypseModeCopy } from './apocalypse-copy.mjs';

export default {
'sp.message_info.sent': '(sent {time})',
'sp.message_info.speed': '{rate} tok/sec',
'sp.message_info.tokens': '{count} tokens',
'sp.message_info.duration': '{seconds}s',
'sp.message_info.finish': 'Stop reason: {reason}',
'sp.message_info.hint': 'Click to show message info',
...chromeWebStoreLocale,
'sp.streaming.fallback': 'Поток ответа был прерван; этот запрос Ask повторяется без потоковой передачи.',
'sp.providers.no_setup_group': 'Без настройки',
Expand Down
6 changes: 6 additions & 0 deletions src/chrome/src/ui/locales/th.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,12 @@ import chromeWebStoreLocale from './chrome-web-store.mjs';
import { getApocalypseModeCopy } from './apocalypse-copy.mjs';

export default {
'sp.message_info.sent': '(sent {time})',
'sp.message_info.speed': '{rate} tok/sec',
'sp.message_info.tokens': '{count} tokens',
'sp.message_info.duration': '{seconds}s',
'sp.message_info.finish': 'Stop reason: {reason}',
'sp.message_info.hint': 'Click to show message info',
...chromeWebStoreLocale,
'sp.streaming.fallback': 'การสตรีมคำตอบถูกขัดจังหวะ กำลังลอง Ask รอบนี้อีกครั้งโดยไม่ใช้สตรีม',
'sp.providers.no_setup_group': 'ไม่ต้องตั้งค่า',
Expand Down
Loading
Loading