codeblog-app 1.6.4 → 1.6.5
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.
- package/package.json +1 -1
- package/src/ai/chat.ts +80 -49
package/package.json
CHANGED
package/src/ai/chat.ts
CHANGED
|
@@ -35,64 +35,95 @@ export namespace AIChat {
|
|
|
35
35
|
onToolResult?: (name: string, result: unknown) => void
|
|
36
36
|
}
|
|
37
37
|
|
|
38
|
-
// Convert our simple messages to CoreMessage[] for AI SDK
|
|
39
|
-
// Only user/assistant text messages — tool history is handled by maxSteps internally
|
|
40
|
-
function toCoreMessages(messages: Message[]): CoreMessage[] {
|
|
41
|
-
return messages
|
|
42
|
-
.filter((m) => m.role === "user" || m.role === "assistant")
|
|
43
|
-
.map((m) => ({ role: m.role as "user" | "assistant", content: m.content }))
|
|
44
|
-
}
|
|
45
|
-
|
|
46
38
|
export async function stream(messages: Message[], callbacks: StreamCallbacks, modelID?: string, signal?: AbortSignal) {
|
|
47
39
|
const model = await AIProvider.getModel(modelID)
|
|
48
40
|
log.info("streaming", { model: modelID || AIProvider.DEFAULT_MODEL, messages: messages.length })
|
|
49
41
|
|
|
50
|
-
|
|
42
|
+
// Build history: only user/assistant text (tool context is added per-step below)
|
|
43
|
+
const history: CoreMessage[] = messages
|
|
44
|
+
.filter((m) => m.role === "user" || m.role === "assistant")
|
|
45
|
+
.map((m) => ({ role: m.role as "user" | "assistant", content: m.content }))
|
|
51
46
|
let full = ""
|
|
52
47
|
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
48
|
+
for (let step = 0; step < 5; step++) {
|
|
49
|
+
if (signal?.aborted) break
|
|
50
|
+
|
|
51
|
+
const result = streamText({
|
|
52
|
+
model,
|
|
53
|
+
system: SYSTEM_PROMPT,
|
|
54
|
+
messages: history,
|
|
55
|
+
tools: chatTools,
|
|
56
|
+
maxSteps: 1,
|
|
57
|
+
abortSignal: signal,
|
|
58
|
+
})
|
|
59
|
+
|
|
60
|
+
const calls: Array<{ id: string; name: string; input: unknown; output: unknown }> = []
|
|
61
|
+
|
|
62
|
+
try {
|
|
63
|
+
for await (const part of result.fullStream) {
|
|
64
|
+
if (signal?.aborted) break
|
|
65
|
+
switch (part.type) {
|
|
66
|
+
case "text-delta": {
|
|
67
|
+
const delta = (part as any).text ?? (part as any).textDelta ?? ""
|
|
68
|
+
if (delta) { full += delta; callbacks.onToken?.(delta) }
|
|
69
|
+
break
|
|
70
|
+
}
|
|
71
|
+
case "tool-call": {
|
|
72
|
+
const input = (part as any).input ?? (part as any).args
|
|
73
|
+
callbacks.onToolCall?.(part.toolName, input)
|
|
74
|
+
calls.push({ id: part.toolCallId, name: part.toolName, input, output: undefined })
|
|
75
|
+
break
|
|
76
|
+
}
|
|
77
|
+
case "tool-result": {
|
|
78
|
+
const output = (part as any).output ?? (part as any).result ?? {}
|
|
79
|
+
const name = (part as any).toolName
|
|
80
|
+
callbacks.onToolResult?.(name, output)
|
|
81
|
+
const match = calls.find((c) => c.name === name && c.output === undefined)
|
|
82
|
+
if (match) match.output = output
|
|
83
|
+
break
|
|
84
|
+
}
|
|
85
|
+
case "error": {
|
|
86
|
+
const msg = part.error instanceof Error ? part.error.message : String(part.error)
|
|
87
|
+
log.error("stream part error", { error: msg })
|
|
88
|
+
callbacks.onError?.(part.error instanceof Error ? part.error : new Error(msg))
|
|
89
|
+
break
|
|
90
|
+
}
|
|
87
91
|
}
|
|
88
92
|
}
|
|
93
|
+
} catch (err) {
|
|
94
|
+
const error = err instanceof Error ? err : new Error(String(err))
|
|
95
|
+
log.error("stream error", { error: error.message })
|
|
96
|
+
if (callbacks.onError) callbacks.onError(error)
|
|
97
|
+
else throw error
|
|
98
|
+
return full
|
|
89
99
|
}
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
100
|
+
|
|
101
|
+
if (calls.length === 0) break
|
|
102
|
+
|
|
103
|
+
// AI SDK v6 ModelMessage format:
|
|
104
|
+
// AssistantModelMessage with ToolCallPart uses "input"
|
|
105
|
+
// ToolModelMessage with ToolResultPart uses "output: { type: 'json', value }"
|
|
106
|
+
history.push({
|
|
107
|
+
role: "assistant",
|
|
108
|
+
content: calls.map((c) => ({
|
|
109
|
+
type: "tool-call" as const,
|
|
110
|
+
toolCallId: c.id,
|
|
111
|
+
toolName: c.name,
|
|
112
|
+
input: c.input,
|
|
113
|
+
})),
|
|
114
|
+
} as CoreMessage)
|
|
115
|
+
|
|
116
|
+
history.push({
|
|
117
|
+
role: "tool",
|
|
118
|
+
content: calls.map((c) => ({
|
|
119
|
+
type: "tool-result" as const,
|
|
120
|
+
toolCallId: c.id,
|
|
121
|
+
toolName: c.name,
|
|
122
|
+
output: { type: "json" as const, value: c.output ?? {} },
|
|
123
|
+
})),
|
|
124
|
+
} as CoreMessage)
|
|
125
|
+
|
|
126
|
+
log.info("tool step done", { step, tools: calls.map((c) => c.name) })
|
|
96
127
|
}
|
|
97
128
|
|
|
98
129
|
callbacks.onFinish?.(full || "(No response)")
|