@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 +3 -0
- package/dist/index.mjs +60 -49
- package/dist/proxies/index.mjs +3 -3
- package/package.json +1 -1
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
|
-
|
|
361
|
-
|
|
362
|
-
|
|
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:
|
|
344
|
+
text: prompt
|
|
373
345
|
}]
|
|
374
346
|
}
|
|
375
347
|
});
|
|
376
|
-
if (
|
|
377
|
-
|
|
378
|
-
|
|
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
|
-
|
|
381
|
-
|
|
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) {
|
package/dist/proxies/index.mjs
CHANGED
|
@@ -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
|
|
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: "
|
|
133
|
+
path: "/**/git-upload-pack"
|
|
134
134
|
},
|
|
135
135
|
{
|
|
136
136
|
method: "GET",
|
|
137
|
-
path: "
|
|
137
|
+
path: "/**/info/refs"
|
|
138
138
|
},
|
|
139
139
|
...userAllow
|
|
140
140
|
],
|