@flue/client 0.0.29 → 0.0.31

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/dist/index.d.mts CHANGED
@@ -90,6 +90,8 @@ interface FlueClientOptions {
90
90
  fetch: (request: Request) => Promise<Response>;
91
91
  /** Shell implementation for executing commands in the target environment. */
92
92
  shell: (command: string, options?: ShellOptions) => Promise<ShellResult>;
93
+ /** Enable verbose debug logging (default: false). */
94
+ debug?: boolean;
93
95
  }
94
96
  interface SkillOptions<S extends v.GenericSchema | undefined = undefined> {
95
97
  /** Key-value args serialized into the prompt. */
@@ -138,6 +140,7 @@ declare class FlueClient {
138
140
  private readonly model?;
139
141
  private readonly client;
140
142
  private readonly shellFn;
143
+ private readonly debug;
141
144
  constructor(options: FlueClientOptions);
142
145
  /** Run a named skill with a result schema. */
143
146
  skill<S extends v.GenericSchema>(name: string, options: SkillOptions<S> & {
package/dist/index.mjs CHANGED
@@ -254,7 +254,7 @@ function buildSkillPrompt(name, args, schema, proxyInstructions) {
254
254
  * @returns The validated, typed result.
255
255
  * @throws {SkillOutputError} If no result block is found or validation fails.
256
256
  */
257
- function extractResult(parts, schema, sessionId) {
257
+ function extractResult(parts, schema, sessionId, debug) {
258
258
  const allText = parts.filter((p) => p.type === "text").map((p) => p.text).join("\n");
259
259
  const resultBlock = extractLastResultBlock(allText);
260
260
  if (resultBlock === null) {
@@ -287,7 +287,7 @@ function extractResult(parts, schema, sessionId) {
287
287
  validationErrors: parsedResult.issues
288
288
  });
289
289
  }
290
- console.log("[flue] extractResult: validated result:", JSON.stringify(parsedResult.output));
290
+ if (debug) console.log("[flue] extractResult: validated result:", JSON.stringify(parsedResult.output));
291
291
  return parsedResult.output;
292
292
  }
293
293
  /**
@@ -316,7 +316,7 @@ const DEFAULT_POLL_TIMEOUT = 3600 * 1e3;
316
316
  * Both `flu.prompt()` and `flu.skill()` delegate to this function after
317
317
  * constructing their own prompt text.
318
318
  */
319
- async function runPrompt(client, workdir, label, prompt, options) {
319
+ async function runPrompt(client, workdir, label, prompt, options, debug) {
320
320
  const { result: schema, model, timeout } = options ?? {};
321
321
  console.log(`[flue] ${label}: starting`);
322
322
  console.log(`[flue] ${label}: creating session`);
@@ -324,87 +324,96 @@ async function runPrompt(client, workdir, label, prompt, options) {
324
324
  body: { title: label },
325
325
  query: { directory: workdir }
326
326
  });
327
- console.log(`[flue] ${label}: session created`, {
327
+ if (debug) console.log(`[flue] ${label}: session created`, {
328
328
  hasData: !!session.data,
329
329
  sessionId: session.data?.id,
330
330
  error: session.error
331
331
  });
332
332
  if (!session.data) throw new Error(`Failed to create OpenCode session for "${label}".`);
333
333
  const sessionId = session.data.id;
334
- const promptStart = Date.now();
335
- console.log(`[flue] ${label}: sending prompt async`);
336
- const asyncResult = await client.session.promptAsync({
337
- path: { id: sessionId },
338
- query: { directory: workdir },
339
- body: {
340
- ...model ? { model } : {},
341
- parts: [{
342
- type: "text",
343
- text: prompt
344
- }]
345
- }
346
- });
347
- console.log(`[flue] ${label}: prompt sent`, {
348
- hasError: !!asyncResult.error,
349
- error: asyncResult.error,
350
- data: asyncResult.data
351
- });
352
- if (asyncResult.error) throw new Error(`Failed to send prompt for "${label}" (session ${sessionId}): ${JSON.stringify(asyncResult.error)}`);
353
- await confirmSessionStarted(client, sessionId, workdir, label);
354
- console.log(`[flue] ${label}: starting polling`);
355
- const parts = await pollUntilIdle(client, sessionId, workdir, label, promptStart, timeout);
356
- const promptElapsed = ((Date.now() - promptStart) / 1e3).toFixed(1);
357
- console.log(`[flue] ${label}: completed (${promptElapsed}s)`);
358
- if (!schema) return;
359
334
  try {
360
- return extractResult(parts, schema, sessionId);
361
- } catch (error) {
362
- if (!(error instanceof SkillOutputError)) throw error;
363
- if (!error.message.includes("---RESULT_START---")) throw error;
364
- console.log(`[flue] ${label}: result extraction failed, sending follow-up prompt to request result`);
365
- const followUpResult = await client.session.promptAsync({
335
+ const promptStart = Date.now();
336
+ if (debug) console.log(`[flue] ${label}: sending prompt async`);
337
+ const asyncResult = await client.session.promptAsync({
366
338
  path: { id: sessionId },
367
339
  query: { directory: workdir },
368
340
  body: {
369
341
  ...model ? { model } : {},
370
342
  parts: [{
371
343
  type: "text",
372
- text: buildResultExtractionPrompt(schema)
344
+ text: prompt
373
345
  }]
374
346
  }
375
347
  });
376
- if (followUpResult.error) {
377
- if (followUpResult.error instanceof Error) followUpResult.error.cause = error;
378
- throw followUpResult.error;
348
+ if (debug) console.log(`[flue] ${label}: prompt sent`, {
349
+ hasError: !!asyncResult.error,
350
+ error: asyncResult.error,
351
+ data: asyncResult.data
352
+ });
353
+ if (asyncResult.error) throw new Error(`Failed to send prompt for "${label}" (session ${sessionId}): ${JSON.stringify(asyncResult.error)}`);
354
+ await confirmSessionStarted(client, sessionId, workdir, label, debug);
355
+ if (debug) console.log(`[flue] ${label}: starting polling`);
356
+ const parts = await pollUntilIdle(client, sessionId, workdir, label, promptStart, timeout, debug);
357
+ const promptElapsed = ((Date.now() - promptStart) / 1e3).toFixed(1);
358
+ if (debug) console.log(`[flue] ${label}: completed (${promptElapsed}s)`);
359
+ if (!schema) return;
360
+ try {
361
+ return extractResult(parts, schema, sessionId, debug);
362
+ } catch (error) {
363
+ if (!(error instanceof SkillOutputError)) throw error;
364
+ if (!error.message.includes("---RESULT_START---")) throw error;
365
+ console.log(`[flue] ${label}: result extraction failed, sending follow-up prompt to request result`);
366
+ const followUpResult = await client.session.promptAsync({
367
+ path: { id: sessionId },
368
+ query: { directory: workdir },
369
+ body: {
370
+ ...model ? { model } : {},
371
+ parts: [{
372
+ type: "text",
373
+ text: buildResultExtractionPrompt(schema)
374
+ }]
375
+ }
376
+ });
377
+ if (followUpResult.error) {
378
+ if (followUpResult.error instanceof Error) followUpResult.error.cause = error;
379
+ throw followUpResult.error;
380
+ }
381
+ await confirmSessionStarted(client, sessionId, workdir, label, debug);
382
+ return extractResult(await pollUntilIdle(client, sessionId, workdir, label, Date.now(), timeout, debug), schema, sessionId, debug);
379
383
  }
380
- await confirmSessionStarted(client, sessionId, workdir, label);
381
- return extractResult(await pollUntilIdle(client, sessionId, workdir, label, Date.now(), timeout), schema, sessionId);
384
+ } finally {
385
+ try {
386
+ await client.session.delete({
387
+ path: { id: sessionId },
388
+ query: { directory: workdir }
389
+ });
390
+ } catch {}
382
391
  }
383
392
  }
384
393
  /**
385
394
  * Run a named skill: builds the skill prompt from the name + args + schema,
386
395
  * then delegates to runPrompt().
387
396
  */
388
- async function runSkill(client, workdir, name, options, proxyInstructions) {
397
+ async function runSkill(client, workdir, name, options, proxyInstructions, debug) {
389
398
  const { args, result: schema, model, timeout } = options ?? {};
390
399
  const prompt = buildSkillPrompt(name, args, schema, proxyInstructions);
391
400
  return runPrompt(client, workdir, `skill("${name}")`, prompt, {
392
401
  result: schema,
393
402
  model,
394
403
  timeout
395
- });
404
+ }, debug);
396
405
  }
397
406
  /**
398
407
  * After promptAsync, confirm that OpenCode actually started processing the session.
399
408
  * Polls quickly (1s) to detect the session appearing as "busy" or a user message being recorded.
400
409
  * Fails fast (~15s) instead of letting the poll loop run for 5 minutes.
401
410
  */
402
- async function confirmSessionStarted(client, sessionId, workdir, label) {
411
+ async function confirmSessionStarted(client, sessionId, workdir, label, debug) {
403
412
  const maxAttempts = 15;
404
413
  for (let attempt = 1; attempt <= maxAttempts; attempt++) {
405
414
  await sleep(1e3);
406
415
  if (((await client.session.status({ query: { directory: workdir } })).data?.[sessionId])?.type === "busy") {
407
- console.log(`[flue] ${label}: session confirmed running`);
416
+ if (debug) console.log(`[flue] ${label}: session confirmed running`);
408
417
  return;
409
418
  }
410
419
  const messages = (await client.session.messages({
@@ -412,13 +421,13 @@ async function confirmSessionStarted(client, sessionId, workdir, label) {
412
421
  query: { directory: workdir }
413
422
  })).data;
414
423
  if (messages && messages.length > 0) {
415
- console.log(`[flue] ${label}: session confirmed (${messages.length} messages)`);
424
+ if (debug) console.log(`[flue] ${label}: session confirmed (${messages.length} messages)`);
416
425
  return;
417
426
  }
418
427
  }
419
428
  throw new Error(`"${label}" failed to start: session ${sessionId} has no messages after 15s.\nThe prompt was accepted but OpenCode never began processing it.\nThis usually means no model is configured. Pass --model to the flue CLI or set "model" in opencode.json.`);
420
429
  }
421
- async function pollUntilIdle(client, sessionId, workdir, label, startTime, timeout) {
430
+ async function pollUntilIdle(client, sessionId, workdir, label, startTime, timeout, debug) {
422
431
  const maxPollTime = timeout ?? DEFAULT_POLL_TIMEOUT;
423
432
  let emptyPolls = 0;
424
433
  let pollCount = 0;
@@ -487,11 +496,13 @@ var FlueClient = class {
487
496
  model;
488
497
  client;
489
498
  shellFn;
499
+ debug;
490
500
  constructor(options) {
491
501
  this.proxyInstructions = options.proxies?.map((p) => p.instructions).filter((i) => !!i) ?? [];
492
502
  this.workdir = options.workdir;
493
503
  this.model = options.model;
494
504
  this.shellFn = options.shell;
505
+ this.debug = options.debug ?? false;
495
506
  this.client = createOpencodeClient({
496
507
  baseUrl: options.opencodeUrl ?? "http://localhost:48765",
497
508
  directory: this.workdir,
@@ -503,7 +514,7 @@ var FlueClient = class {
503
514
  ...options,
504
515
  model: options?.model ?? this.model
505
516
  };
506
- return runSkill(this.client, this.workdir, name, mergedOptions, this.proxyInstructions);
517
+ return runSkill(this.client, this.workdir, name, mergedOptions, this.proxyInstructions, this.debug);
507
518
  }
508
519
  async prompt(promptText, options) {
509
520
  const schema = options?.result;
@@ -522,7 +533,7 @@ var FlueClient = class {
522
533
  return runPrompt(this.client, this.workdir, label, fullPrompt, {
523
534
  result: options?.result,
524
535
  model: options?.model ?? this.model
525
- });
536
+ }, this.debug);
526
537
  }
527
538
  /** Execute a shell command with scoped environment variables. */
528
539
  async shell(command, options) {
@@ -91,7 +91,7 @@ function github(opts) {
91
91
  "user-agent": "flue-proxy"
92
92
  },
93
93
  policy: resolvedPolicy,
94
- setup: ["git config --global url.\"{{proxyUrl}}/\".insteadOf \"https://github.com/\"", "git config --global http.{{proxyUrl}}/.extraheader \"Authorization: Bearer proxy-placeholder\""],
94
+ setup: ["git config --global url.\"{{proxyUrl}}/\".insteadOf \"https://github.com/\"", "git config --global http.{{proxyUrl}}/.extraheader \"Authorization: Bearer {{proxyToken}}\""],
95
95
  denyResponse
96
96
  }];
97
97
  };
@@ -130,11 +130,11 @@ function resolveGitHubPolicy(policy) {
130
130
  },
131
131
  {
132
132
  method: "POST",
133
- path: "/*/git-upload-pack"
133
+ path: "/**/git-upload-pack"
134
134
  },
135
135
  {
136
136
  method: "GET",
137
- path: "/*/info/refs"
137
+ path: "/**/info/refs"
138
138
  },
139
139
  ...userAllow
140
140
  ],
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@flue/client",
3
- "version": "0.0.29",
3
+ "version": "0.0.31",
4
4
  "type": "module",
5
5
  "license": "Apache-2.0",
6
6
  "exports": {