Wave 0: shared contracts + extension points
- src/core/contracts.ts: plan tree / graph / ledger / job protocol / artifact
registry types — the single source of truth for all workstreams
- tools: per-request factory createTeacherTools({chatId}) composing
questionTools + planTools (stub) + jobTools (stub); quiz gains nodeId
- prompt composition: system-prompt.md + sorted src/agent/prompts/*.md
- ChatRecord.tree + saveTree; pending gate scoped to quiz/ask (+findPendingJobCall)
- chat.tsx: artifact renderer extension point + ChatActionsContext.syncFromServer
This commit is contained in:
+25
-3
@@ -1,9 +1,13 @@
|
||||
import { isToolUIPart, type UIMessage } from "ai";
|
||||
|
||||
/**
|
||||
* The pending-question gate: an unanswered interaction tool part (quiz/ask)
|
||||
* The pending-question gate: an unanswered QUESTION tool part (quiz/ask)
|
||||
* blocks the composer — one question at a time, mirroring the teacher's
|
||||
* prompt contract. Derived client state; the server enforces nothing.
|
||||
*
|
||||
* Job tool parts (research/demo/visual) are NOT returned here — they gate
|
||||
* sending separately with a "waiting for <job>" affordance (see contracts.ts
|
||||
* "THE ASYNC PROTOCOL" §5; use findPendingJobCall).
|
||||
*/
|
||||
export interface PendingToolCall {
|
||||
toolCallId: string;
|
||||
@@ -12,15 +16,33 @@ export interface PendingToolCall {
|
||||
input: unknown;
|
||||
}
|
||||
|
||||
export function findPendingToolCall(messages: UIMessage[]): PendingToolCall | null {
|
||||
/** Tools that render in the question dock and gate the composer. */
|
||||
export const QUESTION_TOOLS = ["quiz", "ask_user_question"] as const;
|
||||
|
||||
/** Tools fulfilled by background sub-agent jobs (W2/W6). */
|
||||
export const JOB_TOOLS = ["research", "demo", "visual"] as const;
|
||||
|
||||
function findPending(messages: UIMessage[], names: readonly string[]): PendingToolCall | null {
|
||||
for (let m = messages.length - 1; m >= 0; m--) {
|
||||
const msg = messages[m]!;
|
||||
for (let p = msg.parts.length - 1; p >= 0; p--) {
|
||||
const part = msg.parts[p]!;
|
||||
if (isToolUIPart(part) && part.state === "input-available") {
|
||||
return { toolCallId: part.toolCallId, tool: part.type.slice("tool-".length), input: part.input };
|
||||
const tool = part.type.slice("tool-".length);
|
||||
if (names.includes(tool)) {
|
||||
return { toolCallId: part.toolCallId, tool, input: part.input };
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
export function findPendingToolCall(messages: UIMessage[]): PendingToolCall | null {
|
||||
return findPending(messages, QUESTION_TOOLS);
|
||||
}
|
||||
|
||||
/** The running background job, if any (input-available research/demo/visual part). */
|
||||
export function findPendingJobCall(messages: UIMessage[]): PendingToolCall | null {
|
||||
return findPending(messages, JOB_TOOLS);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user