@@ -3,17 +3,14 @@ import { tools } from './tools.js'
33/** @type {'text' | 'tool' } */
44let outputMode = 'text' // default output mode
55
6- function systemPrompt ( ) {
7- return 'You are a machine learning web application named "Hyperparam" running on a CLI terminal.'
6+ const instructions =
7+ 'You are a machine learning web application named "Hyperparam" running on a CLI terminal.'
88 + '\nYou assist users with analyzing and exploring datasets, particularly in parquet format.'
99 + ' The website and api are available at hyperparam.app.'
1010 + ' The Hyperparam CLI tool can list and explore local parquet files.'
1111 + '\nYou are on a terminal and can only output: text, emojis, terminal colors, and terminal formatting.'
1212 + ' Don\'t add additional markdown or html formatting unless requested.'
1313 + ( process . stdout . isTTY ? ` The terminal width is ${ process . stdout . columns } characters.` : '' )
14- }
15- /** @type {Message } */
16- const systemMessage = { role : 'system' , content : systemPrompt ( ) }
1714
1815const colors = {
1916 system : '\x1b[36m' , // cyan
@@ -24,12 +21,13 @@ const colors = {
2421}
2522
2623/**
27- * @import { Message } from './types.d.ts'
28- * @param {Object } chatInput
29- * @returns {Promise<Message > }
24+ * @import { ResponsesInput, ResponseInputItem } from './types.d.ts'
25+ * @param {ResponsesInput } chatInput
26+ * @returns {Promise<ResponseInputItem[] > }
3027 */
3128async function sendToServer ( chatInput ) {
32- const response = await fetch ( 'https://hyperparam.app/api/functions/openai/chat' , {
29+ // Send the request to the server
30+ const response = await fetch ( 'https://hyperparam.app/api/functions/openai/responses' , {
3331 method : 'POST' ,
3432 headers : { 'Content-Type' : 'application/json' } ,
3533 body : JSON . stringify ( chatInput ) ,
@@ -40,8 +38,8 @@ async function sendToServer(chatInput) {
4038 }
4139
4240 // Process the streaming response
43- /** @type {Message } */
44- const streamResponse = { role : 'assistant' , content : '' }
41+ /** @type {ResponseInputItem[] } */
42+ const incoming = [ ]
4543 const reader = response . body ?. getReader ( )
4644 if ( ! reader ) throw new Error ( 'No response body' )
4745 const decoder = new TextDecoder ( )
@@ -66,14 +64,31 @@ async function sendToServer(chatInput) {
6664 write ( '\n' )
6765 }
6866 outputMode = 'text'
69- streamResponse . content += chunk . delta
67+
68+ // Append to incoming message
69+ const last = incoming [ incoming . length - 1 ]
70+ if ( last && 'role' in last && last . role === 'assistant' && last . id === chunk . item_id ) {
71+ // Append to existing assistant message
72+ last . content += chunk . delta
73+ } else {
74+ // Create a new incoming message
75+ incoming . push ( { role : 'assistant' , content : chunk . delta , id : chunk . item_id } )
76+ }
77+
7078 write ( chunk . delta )
7179 } else if ( error ) {
7280 console . error ( error )
7381 throw new Error ( error )
74- } else if ( chunk . function ) {
75- streamResponse . tool_calls ??= [ ]
76- streamResponse . tool_calls . push ( chunk )
82+ } else if ( type === 'function_call' ) {
83+ incoming . push ( chunk )
84+ } else if ( type === 'response.output_item.done' && chunk . item . type === 'reasoning' ) {
85+ /** @type {import('./types.d.ts').ReasoningItem } */
86+ const reasoningItem = {
87+ type : 'reasoning' ,
88+ id : chunk . item . id ,
89+ summary : chunk . item . summary ,
90+ }
91+ incoming . push ( reasoningItem )
7792 } else if ( ! chunk . key ) {
7893 console . log ( 'Unknown chunk' , chunk )
7994 }
@@ -82,53 +97,66 @@ async function sendToServer(chatInput) {
8297 }
8398 }
8499 }
85- return streamResponse
100+ return incoming
86101}
87102
88103/**
89104 * Send messages to the server and handle tool calls.
90105 * Will mutate the messages array!
91106 *
92- * @import { ToolCall , ToolHandler } from './types.d.ts'
93- * @param {Message [] } messages
107+ * @import { ResponseFunctionToolCall , ToolHandler } from './types.d.ts'
108+ * @param {ResponseInputItem [] } input
94109 * @returns {Promise<void> }
95110 */
96- async function sendMessages ( messages ) {
111+ async function sendMessages ( input ) {
112+ /** @type {ResponsesInput } */
97113 const chatInput = {
98- model : 'gpt-4o' ,
99- messages,
114+ model : 'gpt-5' ,
115+ instructions,
116+ input,
117+ reasoning : {
118+ effort : 'low' ,
119+ } ,
100120 tools : tools . map ( tool => tool . tool ) ,
101121 }
102- const response = await sendToServer ( chatInput )
103- messages . push ( response )
104- // handle tool results
105- if ( response . tool_calls ?. length ) {
106- /** @type {{ toolCall: ToolCall, tool: ToolHandler, result: Promise<string> }[] } */
107- const toolResults = [ ]
108- for ( const toolCall of response . tool_calls ) {
109- const tool = tools . find ( tool => tool . tool . function . name === toolCall . function . name )
122+ const incoming = await sendToServer ( chatInput )
123+
124+ // handle tool calls
125+ /** @type {{ toolCall: ResponseFunctionToolCall, tool: ToolHandler, result: Promise<string> }[] } */
126+ const toolResults = [ ]
127+
128+ // start handling tool calls
129+ for ( const message of incoming ) {
130+ if ( message . type === 'function_call' ) {
131+ const tool = tools . find ( tool => tool . tool . name === message . name )
110132 if ( tool ) {
111- const args = JSON . parse ( toolCall . function ? .arguments ?? '{}' )
133+ const args = JSON . parse ( message . arguments ?? '{}' )
112134 const result = tool . handleToolCall ( args )
113- toolResults . push ( { toolCall, tool, result } )
135+ toolResults . push ( { toolCall : message , tool, result } )
114136 } else {
115- throw new Error ( `Unknown tool: ${ toolCall . function . name } ` )
137+ throw new Error ( `Unknown tool: ${ message . name } ` )
116138 }
117139 }
118- // tool mode
140+ }
141+
142+ // tool mode
143+ if ( toolResults . length > 0 ) {
119144 if ( outputMode === 'text' ) {
120145 write ( '\n' )
121146 }
122147 outputMode = 'tool' // switch to tool output mode
148+
149+ // Wait for pending tool calls and process results
123150 for ( const toolResult of toolResults ) {
124151 const { toolCall, tool } = toolResult
152+ const { call_id } = toolCall
125153 try {
126- const content = await toolResult . result
154+ const output = await toolResult . result
127155
128156 // Construct function call message
129- const args = JSON . parse ( toolCall . function ?. arguments ?? '{}' )
157+ const args = JSON . parse ( toolCall . arguments )
130158 const entries = Object . entries ( args )
131- let func = toolCall . function . name
159+ let func = toolCall . name
132160 if ( entries . length === 0 ) {
133161 func += '()'
134162 } else {
@@ -137,15 +165,22 @@ async function sendMessages(messages) {
137165 func += `(${ pairs . join ( ', ' ) } )`
138166 }
139167 write ( colors . tool , `${ tool . emoji } ${ func } ` , colors . normal , '\n' )
140- messages . push ( { role : 'tool ' , content , tool_call_id : toolCall . id } )
168+ incoming . push ( { type : 'function_call_output ' , output , call_id } )
141169 } catch ( error ) {
142- write ( colors . error , `\nError calling tool ${ toolCall . function . name } : ${ error . message } ` , colors . normal )
143- messages . push ( { role : 'tool' , content : `Error calling tool ${ toolCall . function . name } : ${ error . message } ` , tool_call_id : toolCall . id } )
170+ const message = error instanceof Error ? error . message : String ( error )
171+ const toolName = toolCall . name ?? toolCall . id
172+ write ( colors . error , `\nError calling tool ${ toolName } : ${ message } ` , colors . normal )
173+ incoming . push ( { type : 'function_call_output' , output : `Error calling tool ${ toolName } : ${ message } ` , call_id } )
144174 }
145175 }
146176
177+ input . push ( ...incoming )
178+
147179 // send messages with tool results
148- await sendMessages ( messages )
180+ await sendMessages ( input )
181+ } else {
182+ // no tool calls, just append incoming messages
183+ input . push ( ...incoming )
149184 }
150185}
151186
@@ -196,8 +231,8 @@ function writeWithColor() {
196231}
197232
198233export function chat ( ) {
199- /** @type {Message [] } */
200- const messages = [ systemMessage ]
234+ /** @type {ResponseInputItem [] } */
235+ const messages = [ ]
201236 process . stdin . setEncoding ( 'utf-8' )
202237
203238 write ( colors . system , 'question: ' , colors . normal )
0 commit comments