Skip to content

Commit 2407e2b

Browse files
committed
feat: added logger in handleSteps
🤖 Generated with Codebuff Co-Authored-By: Codebuff <noreply@codebuff.com>
1 parent e106c6d commit 2407e2b

File tree

6 files changed

+230
-8
lines changed

6 files changed

+230
-8
lines changed

.agents/types/agent-definition.ts

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -179,7 +179,7 @@ export interface AgentDefinition {
179179
* }
180180
* }
181181
*/
182-
handleSteps?: (context: AgentStepContext) => Generator<
182+
handleSteps?: (context: AgentStepContext, logger?: Logger) => Generator<
183183
ToolCall | 'STEP' | 'STEP_ALL',
184184
void,
185185
{
@@ -194,6 +194,13 @@ export interface AgentDefinition {
194194
// Supporting Types
195195
// ============================================================================
196196

197+
export interface Logger {
198+
debug: (data: any, msg?: string) => void
199+
info: (data: any, msg?: string) => void
200+
warn: (data: any, msg?: string) => void
201+
error: (data: any, msg?: string) => void
202+
}
203+
197204
export interface AgentState {
198205
agentId: string
199206
runId: string

backend/src/run-programmatic-step.ts

Lines changed: 87 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,18 @@ export async function runProgrammaticStep(
7474
stepNumber: number
7575
},
7676
): Promise<{ agentState: AgentState; endTurn: boolean; stepNumber: number }> {
77+
logger.info(
78+
{
79+
agentType: template.id,
80+
runId: agentState.runId,
81+
hasHandleSteps: !!template.handleSteps,
82+
handleStepsType: typeof template.handleSteps,
83+
stepNumber,
84+
stepsComplete,
85+
},
86+
'runProgrammaticStep: Starting programmatic step execution',
87+
)
88+
7789
if (!template.handleSteps) {
7890
throw new Error('No step handler found for agent template ' + template.id)
7991
}
@@ -86,9 +98,37 @@ export async function runProgrammaticStep(
8698
let generator = runIdToGenerator[agentState.runId]
8799
let sandbox = sandboxManager.getSandbox(agentState.runId)
88100

89-
// Check if we need to initialize a generator (either native or QuickJS-based)
101+
// Check if we need to initialize a generator
90102
if (!generator && !sandbox) {
103+
const createLogMethod =
104+
(level: 'debug' | 'info' | 'warn' | 'error') =>
105+
(data: any, msg?: string) => {
106+
logger[level](data, msg) // Log to backend
107+
sendAction(ws, {
108+
type: 'handlesteps-log-chunk',
109+
userInputId,
110+
agentId: agentState.agentId,
111+
level,
112+
data,
113+
message: msg,
114+
})
115+
}
116+
117+
const streamingLogger = {
118+
debug: createLogMethod('debug'),
119+
info: createLogMethod('info'),
120+
warn: createLogMethod('warn'),
121+
error: createLogMethod('error'),
122+
}
123+
91124
if (typeof template.handleSteps === 'string') {
125+
logger.info(
126+
{
127+
agentType: template.id,
128+
runId: agentState.runId,
129+
},
130+
'runProgrammaticStep: Initializing QuickJS sandbox for string-based generator',
131+
)
92132
// Initialize QuickJS sandbox for string-based generator
93133
sandbox = await sandboxManager.getOrCreateSandbox(
94134
agentState.runId,
@@ -98,15 +138,35 @@ export async function runProgrammaticStep(
98138
prompt,
99139
params,
100140
},
141+
undefined, // config
142+
streamingLogger, // pass the streaming logger instance
101143
)
102144
} else {
145+
logger.info(
146+
{
147+
agentType: template.id,
148+
runId: agentState.runId,
149+
},
150+
'runProgrammaticStep: Initializing native JavaScript generator',
151+
)
103152
// Initialize native generator
104-
generator = template.handleSteps({
105-
agentState,
106-
prompt,
107-
params,
108-
})
153+
generator = (template.handleSteps as any)(
154+
{
155+
agentState,
156+
prompt,
157+
params,
158+
},
159+
streamingLogger,
160+
)
109161
runIdToGenerator[agentState.runId] = generator
162+
logger.info(
163+
{
164+
agentType: template.id,
165+
runId: agentState.runId,
166+
generatorInitialized: !!generator,
167+
},
168+
'runProgrammaticStep: Native generator initialized successfully',
169+
)
110170
}
111171
}
112172

@@ -168,6 +228,17 @@ export async function runProgrammaticStep(
168228
creditsBefore = state.agentState.directCreditsUsed
169229
childrenBefore = state.agentState.childRunIds.length
170230

231+
logger.info(
232+
{
233+
agentType: template.id,
234+
runId: agentState.runId,
235+
usingSandbox: !!sandbox,
236+
usingGenerator: !!generator,
237+
stepsComplete,
238+
},
239+
'runProgrammaticStep: About to execute generator step',
240+
)
241+
171242
const result = sandbox
172243
? await sandbox.executeStep({
173244
agentState: getPublicAgentState(state.agentState),
@@ -180,6 +251,16 @@ export async function runProgrammaticStep(
180251
stepsComplete,
181252
})
182253

254+
logger.info(
255+
{
256+
agentType: template.id,
257+
runId: agentState.runId,
258+
resultDone: result.done,
259+
resultValue: result.value,
260+
},
261+
'runProgrammaticStep: Generator step executed, got result',
262+
)
263+
183264
if (result.done) {
184265
endTurn = true
185266
break

backend/src/util/quickjs-sandbox.ts

Lines changed: 66 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,12 @@ export class QuickJSSandbox {
5252
generatorCode: string,
5353
initialInput: any,
5454
config: SandboxConfig = {},
55+
logger?: {
56+
debug: (data: any, msg?: string) => void
57+
info: (data: any, msg?: string) => void
58+
warn: (data: any, msg?: string) => void
59+
error: (data: any, msg?: string) => void
60+
},
5561
): Promise<QuickJSSandbox> {
5662
const {
5763
memoryLimit = 1024 * 1024 * 20, // 20MB
@@ -80,6 +86,45 @@ export class QuickJSSandbox {
8086
const context = runtime.newContext()
8187

8288
try {
89+
// Set up logger handler
90+
const loggerHandler = context.newFunction(
91+
'_loggerHandler',
92+
(level, data, msg) => {
93+
try {
94+
const levelStr = context.getString(level)
95+
let dataObj: any
96+
let msgStr: string | undefined
97+
98+
try {
99+
dataObj = data ? JSON.parse(context.getString(data)) : undefined
100+
} catch {
101+
dataObj = context.getString(data)
102+
}
103+
104+
msgStr = msg ? context.getString(msg) : undefined
105+
106+
if (logger) {
107+
if (levelStr === 'debug' && logger.debug) {
108+
logger.debug(dataObj, msgStr)
109+
} else if (levelStr === 'info' && logger.info) {
110+
logger.info(dataObj, msgStr)
111+
} else if (levelStr === 'warn' && logger.warn) {
112+
logger.warn(dataObj, msgStr)
113+
} else if (levelStr === 'error' && logger.error) {
114+
logger.error(dataObj, msgStr)
115+
}
116+
}
117+
} catch (err) {
118+
// Fallback for logging errors
119+
if (logger?.error) {
120+
logger.error({ error: err }, 'Logger handler error')
121+
}
122+
}
123+
},
124+
)
125+
126+
context.setProp(context.global, '_loggerHandler', loggerHandler)
127+
loggerHandler.dispose()
83128
// Inject safe globals and the generator function
84129
const setupCode = `
85130
// Safe console implementation
@@ -89,11 +134,24 @@ export class QuickJSSandbox {
89134
warn: (...args) => undefined
90135
};
91136
137+
// Logger implementation
138+
const createLogMethod = (level) => (data, msg) =>
139+
globalThis._loggerHandler(level,
140+
typeof data === 'object' ? JSON.stringify(data) : String(data),
141+
msg ? String(msg) : undefined);
142+
143+
const logger = {
144+
debug: createLogMethod('debug'),
145+
info: createLogMethod('info'),
146+
warn: createLogMethod('warn'),
147+
error: createLogMethod('error')
148+
};
149+
92150
// Agent function
93151
const handleSteps = ${generatorCode};
94152
95153
// Create generator instance
96-
let generator = handleSteps(${JSON.stringify(initialInput)});
154+
let generator = handleSteps(${JSON.stringify(initialInput)}, logger);
97155
98156
// Generator management
99157
globalThis._generator = generator;
@@ -213,6 +271,12 @@ export class SandboxManager {
213271
generatorCode: string,
214272
initialInput: any,
215273
config?: SandboxConfig,
274+
logger?: {
275+
debug: (data: any, msg?: string) => void
276+
info: (data: any, msg?: string) => void
277+
warn: (data: any, msg?: string) => void
278+
error: (data: any, msg?: string) => void
279+
},
216280
): Promise<QuickJSSandbox> {
217281
const existing = this.sandboxes.get(runId)
218282
if (existing && existing.isInitialized()) {
@@ -229,6 +293,7 @@ export class SandboxManager {
229293
generatorCode,
230294
initialInput,
231295
config,
296+
logger,
232297
)
233298
this.sandboxes.set(runId, sandbox)
234299
return sandbox

common/src/actions.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -130,6 +130,14 @@ export const SERVER_ACTION_SCHEMA = z.discriminatedUnion('type', [
130130
chunk: z.string(),
131131
prompt: z.string().optional(),
132132
}),
133+
z.object({
134+
type: z.literal('handlesteps-log-chunk'),
135+
userInputId: z.string(),
136+
agentId: z.string(),
137+
level: z.enum(['debug', 'info', 'warn', 'error']),
138+
data: z.any(),
139+
message: z.string().optional(),
140+
}),
133141
PromptResponseSchema,
134142
z.object({
135143
type: z.literal('read-files'),

common/src/types/dynamic-agent-template.ts

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,26 @@ export type PromptField = z.infer<typeof PromptFieldSchema>
6868

6969
const functionSchema = <T extends z.core.$ZodFunction>(schema: T) =>
7070
z.custom<Parameters<T['implement']>[0]>((fn: any) => schema.implement(fn))
71+
// Schema for the Logger interface
72+
const LoggerSchema = z.object({
73+
debug: z.function({
74+
input: [z.any(), z.string().optional()],
75+
output: z.void(),
76+
}),
77+
info: z.function({
78+
input: [z.any(), z.string().optional()],
79+
output: z.void(),
80+
}),
81+
warn: z.function({
82+
input: [z.any(), z.string().optional()],
83+
output: z.void(),
84+
}),
85+
error: z.function({
86+
input: [z.any(), z.string().optional()],
87+
output: z.void(),
88+
}),
89+
})
90+
7191
// Schema for validating handleSteps function signature
7292
const HandleStepsSchema = functionSchema(
7393
z.function({
@@ -81,6 +101,7 @@ const HandleStepsSchema = functionSchema(
81101
prompt: z.string().optional(),
82102
params: z.any().optional(),
83103
}),
104+
LoggerSchema.optional(),
84105
],
85106
output: z.any(),
86107
}),

npm-app/src/client.ts

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -951,6 +951,46 @@ export class Client {
951951
// Refresh display if we're currently viewing this agent
952952
refreshSubagentDisplay(agentId)
953953
})
954+
955+
// Handle handleSteps log streaming
956+
this.webSocket.subscribe('handlesteps-log-chunk', (action) => {
957+
const { agentId, level, data, message } = action
958+
959+
// Format the log message for display
960+
const formattedMessage = this.formatLogMessage(level, data, message)
961+
962+
// Display the log message immediately
963+
if (formattedMessage) {
964+
process.stdout.write(formattedMessage + '\n')
965+
}
966+
})
967+
}
968+
969+
private formatLogMessage(level: string, data: any, message?: string): string {
970+
const timestamp = new Date().toISOString().substring(11, 23) // HH:MM:SS.mmm
971+
const levelColors = { debug: blue, info: green, warn: yellow, error: red }
972+
const levelColor =
973+
levelColors[level as keyof typeof levelColors] || ((s: string) => s)
974+
975+
const timeTag = `[${timestamp}]`
976+
const levelTag = levelColor(`[${level.toUpperCase()}]`)
977+
const dataStr = this.serializeLogData(data)
978+
979+
return [timeTag, levelTag, message, dataStr].filter(Boolean).join(' ')
980+
}
981+
982+
private serializeLogData(data: any): string {
983+
if (data === undefined || data === null) return ''
984+
985+
if (typeof data === 'object') {
986+
try {
987+
return JSON.stringify(data, null, 2)
988+
} catch {
989+
return String(data)
990+
}
991+
}
992+
993+
return String(data)
954994
}
955995

956996
private showUsageWarning() {

0 commit comments

Comments
 (0)