完善创作节点循环与等待态交互,并大幅打磨 Web 壳与会话运行时。
- 节点循环:配方开局直坐 DAG 第一步;验收后先问下一步意向,再确认开干并可钉编排参数;「按意见修改」只重跑当前节点。 - 询问分流:能力 opening 走说话面引导,可跳过追问按题干去重;追问与产物同轮挂载,避免拆成两段历史。 - 运行时:补强 revision 重跑、创作流程合并/进度指针、工具循环与 worker 执行;新增运行日志与 web:watch。 - 前端:统一等待态文案与底栏主按钮,完善询问卡/产物验收/呈现壳样式与交互。 - 同步世界模拟器模块提示、编排文档与相关测试。 Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
+147
-106
@@ -5,6 +5,7 @@ import type {
|
||||
CompleteWithToolsResult,
|
||||
LlmProvider,
|
||||
StreamCallbacks,
|
||||
TokenUsage,
|
||||
} from "./client.js";
|
||||
import {
|
||||
recordTokenUsage,
|
||||
@@ -15,6 +16,7 @@ import {
|
||||
buildContextTrace,
|
||||
type LlmContextTrace,
|
||||
} from "../types/context-trace.js";
|
||||
import { debugLog, labelCaller, labelLlmMode, labelTool } from "../log.js";
|
||||
|
||||
export type LlmTrackingContext = {
|
||||
sessionId?: string;
|
||||
@@ -41,6 +43,77 @@ function capturePendingTrace(
|
||||
});
|
||||
}
|
||||
|
||||
function recordUsage(
|
||||
ctx: LlmTrackingContext,
|
||||
caller: string | undefined,
|
||||
model: string | undefined,
|
||||
usage: TokenUsage | undefined,
|
||||
): void {
|
||||
if (!usage) return;
|
||||
const record = recordTokenUsage({
|
||||
sessionId: ctx.sessionId,
|
||||
bookId: ctx.bookId,
|
||||
bookTitle: ctx.bookTitle,
|
||||
orchestratorId: ctx.orchestratorId,
|
||||
caller: caller ?? "unknown",
|
||||
model: model ?? "unknown",
|
||||
promptTokens: usage.promptTokens,
|
||||
completionTokens: usage.completionTokens,
|
||||
totalTokens: usage.totalTokens,
|
||||
cachedTokens: usage.cachedTokens,
|
||||
cacheMissTokens: usage.cacheMissTokens,
|
||||
});
|
||||
ctx.pendingUsage = toMessageTokenUsage(record);
|
||||
}
|
||||
|
||||
function summarizeComplete(result: CompleteResult): string {
|
||||
const bits = [
|
||||
result.model ? `模型=${result.model}` : "",
|
||||
`思维链=${result.reasoning?.trim().length ?? 0}字`,
|
||||
`正文=${result.content?.length ?? 0}字`,
|
||||
];
|
||||
if (result.usage) {
|
||||
bits.push(`用量=${result.usage.promptTokens}+${result.usage.completionTokens}`);
|
||||
}
|
||||
return bits.filter(Boolean).join(" ");
|
||||
}
|
||||
|
||||
function summarizeTools(result: CompleteWithToolsResult): string {
|
||||
const tools =
|
||||
result.toolCalls.map((t) => labelTool(t.name)).join("、") || "无";
|
||||
const bits = [
|
||||
result.model ? `模型=${result.model}` : "",
|
||||
`思维链=${result.reasoning?.trim().length ?? 0}字`,
|
||||
`正文=${result.content?.length ?? 0}字`,
|
||||
`工具=${tools}`,
|
||||
];
|
||||
if (result.usage) {
|
||||
bits.push(`用量=${result.usage.promptTokens}+${result.usage.completionTokens}`);
|
||||
}
|
||||
return bits.filter(Boolean).join(" ");
|
||||
}
|
||||
|
||||
async function loggedLlm<T>(
|
||||
kind: string,
|
||||
caller: string | undefined,
|
||||
run: () => Promise<T>,
|
||||
summarize: (result: T) => string,
|
||||
): Promise<T> {
|
||||
const who = labelCaller(caller);
|
||||
const mode = labelLlmMode(kind);
|
||||
const t0 = Date.now();
|
||||
debugLog("llm", `开始 ${who} · ${mode}`);
|
||||
try {
|
||||
const result = await run();
|
||||
debugLog("llm", `结束 ${who} · ${mode} ${Date.now() - t0}毫秒 ${summarize(result)}`);
|
||||
return result;
|
||||
} catch (err) {
|
||||
const detail = err instanceof Error ? err.message : String(err);
|
||||
debugLog("llm", `失败 ${who} · ${mode} ${Date.now() - t0}毫秒 ${detail.slice(0, 160)}`);
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
|
||||
export class TokenTrackingProvider implements LlmProvider {
|
||||
constructor(
|
||||
private readonly inner: LlmProvider,
|
||||
@@ -51,29 +124,21 @@ export class TokenTrackingProvider implements LlmProvider {
|
||||
messages: Parameters<LlmProvider["complete"]>[0],
|
||||
options?: CompleteOptions,
|
||||
): Promise<CompleteResult> {
|
||||
const result = await this.inner.complete(messages, options);
|
||||
const ctx = this.getContext();
|
||||
capturePendingTrace(ctx, messages, options?.caller, result.model);
|
||||
if (result.usage) {
|
||||
const record = recordTokenUsage({
|
||||
sessionId: ctx.sessionId,
|
||||
bookId: ctx.bookId,
|
||||
bookTitle: ctx.bookTitle,
|
||||
orchestratorId: ctx.orchestratorId,
|
||||
caller: options?.caller ?? "unknown",
|
||||
model: result.model ?? "unknown",
|
||||
promptTokens: result.usage.promptTokens,
|
||||
completionTokens: result.usage.completionTokens,
|
||||
totalTokens: result.usage.totalTokens,
|
||||
cachedTokens: result.usage.cachedTokens,
|
||||
cacheMissTokens: result.usage.cacheMissTokens,
|
||||
});
|
||||
ctx.pendingUsage = toMessageTokenUsage(record);
|
||||
}
|
||||
if (result.reasoning?.trim()) {
|
||||
ctx.pendingReasoning = result.reasoning.trim();
|
||||
}
|
||||
return result;
|
||||
return loggedLlm(
|
||||
"complete",
|
||||
options?.caller,
|
||||
async () => {
|
||||
const result = await this.inner.complete(messages, options);
|
||||
const ctx = this.getContext();
|
||||
capturePendingTrace(ctx, messages, options?.caller, result.model);
|
||||
recordUsage(ctx, options?.caller, result.model, result.usage);
|
||||
if (result.reasoning?.trim()) {
|
||||
ctx.pendingReasoning = result.reasoning.trim();
|
||||
}
|
||||
return result;
|
||||
},
|
||||
summarizeComplete,
|
||||
);
|
||||
}
|
||||
|
||||
async completeStream(
|
||||
@@ -88,67 +153,51 @@ export class TokenTrackingProvider implements LlmProvider {
|
||||
if (result.content) callbacks.onContentDelta?.(result.content);
|
||||
return result;
|
||||
}
|
||||
let reasoningBuf = "";
|
||||
const result = await inner.completeStream(messages, options, {
|
||||
onReasoningDelta: (delta) => {
|
||||
reasoningBuf += delta;
|
||||
return loggedLlm(
|
||||
"stream",
|
||||
options?.caller,
|
||||
async () => {
|
||||
let reasoningBuf = "";
|
||||
const result = await inner.completeStream!(messages, options, {
|
||||
onReasoningDelta: (delta) => {
|
||||
reasoningBuf += delta;
|
||||
const ctx = this.getContext();
|
||||
ctx.pendingReasoning = reasoningBuf;
|
||||
callbacks.onReasoningDelta?.(delta);
|
||||
},
|
||||
onContentDelta: callbacks.onContentDelta,
|
||||
});
|
||||
const ctx = this.getContext();
|
||||
ctx.pendingReasoning = reasoningBuf;
|
||||
callbacks.onReasoningDelta?.(delta);
|
||||
capturePendingTrace(ctx, messages, options?.caller, result.model);
|
||||
recordUsage(ctx, options?.caller, result.model, result.usage);
|
||||
if (result.reasoning?.trim()) {
|
||||
ctx.pendingReasoning = result.reasoning.trim();
|
||||
}
|
||||
return result;
|
||||
},
|
||||
onContentDelta: callbacks.onContentDelta,
|
||||
});
|
||||
const ctx = this.getContext();
|
||||
capturePendingTrace(ctx, messages, options?.caller, result.model);
|
||||
if (result.usage) {
|
||||
const record = recordTokenUsage({
|
||||
sessionId: ctx.sessionId,
|
||||
bookId: ctx.bookId,
|
||||
bookTitle: ctx.bookTitle,
|
||||
orchestratorId: ctx.orchestratorId,
|
||||
caller: options?.caller ?? "unknown",
|
||||
model: result.model ?? "unknown",
|
||||
promptTokens: result.usage.promptTokens,
|
||||
completionTokens: result.usage.completionTokens,
|
||||
totalTokens: result.usage.totalTokens,
|
||||
cachedTokens: result.usage.cachedTokens,
|
||||
cacheMissTokens: result.usage.cacheMissTokens,
|
||||
});
|
||||
ctx.pendingUsage = toMessageTokenUsage(record);
|
||||
}
|
||||
if (result.reasoning?.trim()) {
|
||||
ctx.pendingReasoning = result.reasoning.trim();
|
||||
}
|
||||
return result;
|
||||
summarizeComplete,
|
||||
);
|
||||
}
|
||||
|
||||
async completeWithTools(
|
||||
messages: Parameters<LlmProvider["completeWithTools"]>[0],
|
||||
options: CompleteWithToolsOptions,
|
||||
): Promise<CompleteWithToolsResult> {
|
||||
const result = await this.inner.completeWithTools(messages, options);
|
||||
const ctx = this.getContext();
|
||||
capturePendingTrace(ctx, messages, options.caller, result.model);
|
||||
if (result.usage) {
|
||||
const record = recordTokenUsage({
|
||||
sessionId: ctx.sessionId,
|
||||
bookId: ctx.bookId,
|
||||
bookTitle: ctx.bookTitle,
|
||||
orchestratorId: ctx.orchestratorId,
|
||||
caller: options.caller ?? "unknown",
|
||||
model: result.model ?? "unknown",
|
||||
promptTokens: result.usage.promptTokens,
|
||||
completionTokens: result.usage.completionTokens,
|
||||
totalTokens: result.usage.totalTokens,
|
||||
cachedTokens: result.usage.cachedTokens,
|
||||
cacheMissTokens: result.usage.cacheMissTokens,
|
||||
});
|
||||
ctx.pendingUsage = toMessageTokenUsage(record);
|
||||
}
|
||||
if (result.reasoning?.trim()) {
|
||||
ctx.pendingReasoning = result.reasoning.trim();
|
||||
}
|
||||
return result;
|
||||
return loggedLlm(
|
||||
"tools",
|
||||
options.caller,
|
||||
async () => {
|
||||
const result = await this.inner.completeWithTools(messages, options);
|
||||
const ctx = this.getContext();
|
||||
capturePendingTrace(ctx, messages, options.caller, result.model);
|
||||
recordUsage(ctx, options.caller, result.model, result.usage);
|
||||
if (result.reasoning?.trim()) {
|
||||
ctx.pendingReasoning = result.reasoning.trim();
|
||||
}
|
||||
return result;
|
||||
},
|
||||
summarizeTools,
|
||||
);
|
||||
}
|
||||
|
||||
async completeWithToolsStream(
|
||||
@@ -160,37 +209,29 @@ export class TokenTrackingProvider implements LlmProvider {
|
||||
if (!inner.completeWithToolsStream) {
|
||||
return this.completeWithTools(messages, options);
|
||||
}
|
||||
let reasoningBuf = "";
|
||||
const result = await inner.completeWithToolsStream(messages, options, {
|
||||
onReasoningDelta: (delta) => {
|
||||
reasoningBuf += delta;
|
||||
return loggedLlm(
|
||||
"tools-stream",
|
||||
options.caller,
|
||||
async () => {
|
||||
let reasoningBuf = "";
|
||||
const result = await inner.completeWithToolsStream!(messages, options, {
|
||||
onReasoningDelta: (delta) => {
|
||||
reasoningBuf += delta;
|
||||
const ctx = this.getContext();
|
||||
ctx.pendingReasoning = reasoningBuf;
|
||||
callbacks.onReasoningDelta?.(delta);
|
||||
},
|
||||
onContentDelta: callbacks.onContentDelta,
|
||||
});
|
||||
const ctx = this.getContext();
|
||||
ctx.pendingReasoning = reasoningBuf;
|
||||
callbacks.onReasoningDelta?.(delta);
|
||||
capturePendingTrace(ctx, messages, options.caller, result.model);
|
||||
recordUsage(ctx, options.caller, result.model, result.usage);
|
||||
if (result.reasoning?.trim()) {
|
||||
ctx.pendingReasoning = result.reasoning.trim();
|
||||
}
|
||||
return result;
|
||||
},
|
||||
onContentDelta: callbacks.onContentDelta,
|
||||
});
|
||||
const ctx = this.getContext();
|
||||
capturePendingTrace(ctx, messages, options.caller, result.model);
|
||||
if (result.usage) {
|
||||
const record = recordTokenUsage({
|
||||
sessionId: ctx.sessionId,
|
||||
bookId: ctx.bookId,
|
||||
bookTitle: ctx.bookTitle,
|
||||
orchestratorId: ctx.orchestratorId,
|
||||
caller: options.caller ?? "unknown",
|
||||
model: result.model ?? "unknown",
|
||||
promptTokens: result.usage.promptTokens,
|
||||
completionTokens: result.usage.completionTokens,
|
||||
totalTokens: result.usage.totalTokens,
|
||||
cachedTokens: result.usage.cachedTokens,
|
||||
cacheMissTokens: result.usage.cacheMissTokens,
|
||||
});
|
||||
ctx.pendingUsage = toMessageTokenUsage(record);
|
||||
}
|
||||
if (result.reasoning?.trim()) {
|
||||
ctx.pendingReasoning = result.reasoning.trim();
|
||||
}
|
||||
return result;
|
||||
summarizeTools,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user