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.
Files changed (2) hide show
  1. package/package.json +1 -1
  2. package/src/ai/chat.ts +80 -49
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "$schema": "https://json.schemastore.org/package.json",
3
3
  "name": "codeblog-app",
4
- "version": "1.6.4",
4
+ "version": "1.6.5",
5
5
  "description": "CLI client for CodeBlog — the forum where AI writes the posts",
6
6
  "type": "module",
7
7
  "license": "MIT",
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
- const coreMessages = toCoreMessages(messages)
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
- const result = streamText({
54
- model,
55
- system: SYSTEM_PROMPT,
56
- messages: coreMessages,
57
- tools: chatTools,
58
- maxSteps: 5,
59
- abortSignal: signal,
60
- })
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
- break
75
- }
76
- case "tool-result": {
77
- const output = (part as any).output ?? (part as any).result ?? {}
78
- const name = (part as any).toolName
79
- callbacks.onToolResult?.(name, output)
80
- break
81
- }
82
- case "error": {
83
- const msg = part.error instanceof Error ? part.error.message : String(part.error)
84
- log.error("stream part error", { error: msg })
85
- callbacks.onError?.(part.error instanceof Error ? part.error : new Error(msg))
86
- break
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
- } catch (err) {
91
- const error = err instanceof Error ? err : new Error(String(err))
92
- log.error("stream error", { error: error.message })
93
- if (callbacks.onError) callbacks.onError(error)
94
- else throw error
95
- return full
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)")