@synergenius/flowweaver-pack-weaver 0.7.3 → 0.8.1

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 (47) hide show
  1. package/dist/bot/ai-client.d.ts +10 -0
  2. package/dist/bot/ai-client.d.ts.map +1 -1
  3. package/dist/bot/ai-client.js +27 -0
  4. package/dist/bot/ai-client.js.map +1 -1
  5. package/dist/bot/types.d.ts +1 -1
  6. package/dist/bot/types.d.ts.map +1 -1
  7. package/dist/cli-handlers.d.ts +24 -0
  8. package/dist/cli-handlers.d.ts.map +1 -1
  9. package/dist/cli-handlers.js +205 -34
  10. package/dist/cli-handlers.js.map +1 -1
  11. package/dist/index.d.ts +2 -0
  12. package/dist/index.d.ts.map +1 -1
  13. package/dist/index.js +2 -0
  14. package/dist/index.js.map +1 -1
  15. package/dist/node-types/detect-provider.d.ts.map +1 -1
  16. package/dist/node-types/detect-provider.js +4 -1
  17. package/dist/node-types/detect-provider.js.map +1 -1
  18. package/dist/node-types/exec-validate-retry.d.ts.map +1 -1
  19. package/dist/node-types/exec-validate-retry.js +2 -8
  20. package/dist/node-types/exec-validate-retry.js.map +1 -1
  21. package/dist/node-types/execute-target.d.ts.map +1 -1
  22. package/dist/node-types/execute-target.js +2 -8
  23. package/dist/node-types/execute-target.js.map +1 -1
  24. package/dist/node-types/fix-errors.d.ts.map +1 -1
  25. package/dist/node-types/fix-errors.js +2 -8
  26. package/dist/node-types/fix-errors.js.map +1 -1
  27. package/dist/node-types/genesis-apply-retry.js +2 -8
  28. package/dist/node-types/genesis-apply-retry.js.map +1 -1
  29. package/dist/node-types/genesis-propose.d.ts.map +1 -1
  30. package/dist/node-types/genesis-propose.js +2 -8
  31. package/dist/node-types/genesis-propose.js.map +1 -1
  32. package/dist/node-types/plan-task.d.ts.map +1 -1
  33. package/dist/node-types/plan-task.js +2 -8
  34. package/dist/node-types/plan-task.js.map +1 -1
  35. package/flowweaver.manifest.json +1 -1
  36. package/package.json +1 -1
  37. package/src/bot/ai-client.ts +45 -1
  38. package/src/bot/types.ts +1 -1
  39. package/src/cli-handlers.ts +210 -36
  40. package/src/index.ts +4 -0
  41. package/src/node-types/detect-provider.ts +3 -1
  42. package/src/node-types/exec-validate-retry.ts +2 -7
  43. package/src/node-types/execute-target.ts +2 -13
  44. package/src/node-types/fix-errors.ts +2 -7
  45. package/src/node-types/genesis-apply-retry.ts +2 -7
  46. package/src/node-types/genesis-propose.ts +2 -13
  47. package/src/node-types/plan-task.ts +2 -13
@@ -1,4 +1,4 @@
1
- import { callCli, callApi, parseJsonResponse } from '../bot/ai-client.js';
1
+ import { callAI, parseJsonResponse } from '../bot/ai-client.js';
2
2
  import { auditEmit } from '../bot/audit-logger.js';
3
3
  /**
4
4
  * Sends task + context to the AI provider and gets back a structured
@@ -38,13 +38,7 @@ export async function weaverPlanTask(execute, ctx) {
38
38
  }
39
39
  const userPrompt = `Task: ${task.instruction}\nMode: ${task.mode ?? 'create'}\n${task.targets ? 'Targets: ' + task.targets.join(', ') : ''}\n\nPlan this task. Return a JSON plan with steps and summary.`;
40
40
  try {
41
- let text;
42
- if (pInfo.type === 'anthropic') {
43
- text = await callApi(pInfo.apiKey, pInfo.model ?? 'claude-sonnet-4-6', pInfo.maxTokens ?? 8192, systemPrompt, userPrompt);
44
- }
45
- else {
46
- text = callCli(pInfo.type, systemPrompt + '\n\n' + userPrompt);
47
- }
41
+ const text = await callAI(pInfo, systemPrompt, userPrompt, 8192);
48
42
  const plan = parseJsonResponse(text);
49
43
  console.log(`\x1b[36m→ Plan: ${plan.summary ?? 'generated'}\x1b[0m`);
50
44
  auditEmit('plan-created', { summary: plan.summary, stepCount: plan.steps?.length ?? 0 });
@@ -1 +1 @@
1
- {"version":3,"file":"plan-task.js","sourceRoot":"","sources":["../../src/node-types/plan-task.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"plan-task.js","sourceRoot":"","sources":["../../src/node-types/plan-task.ts"],"names":[],"mappings":"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"}
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "manifestVersion": 1,
3
3
  "name": "@synergenius/flowweaver-pack-weaver",
4
- "version": "0.7.3",
4
+ "version": "0.8.1",
5
5
  "description": "AI bot for Flow Weaver. Execute tasks, run workflows, evolve autonomously.",
6
6
  "engineVersion": ">=0.19.4",
7
7
  "categories": [
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@synergenius/flowweaver-pack-weaver",
3
- "version": "0.7.3",
3
+ "version": "0.8.1",
4
4
  "description": "AI bot for Flow Weaver. Execute tasks, run workflows, evolve autonomously.",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -1,6 +1,6 @@
1
1
  import { execSync, spawn } from 'node:child_process';
2
2
  import { parseStreamLine, extractTextFromChunks } from './cli-stream-parser.js';
3
- import type { StreamChunk } from './types.js';
3
+ import type { ProviderInfo, StreamChunk } from './types.js';
4
4
 
5
5
  // Strip CLAUDECODE from child env so nested claude CLI invocations work.
6
6
  const childEnv = { ...process.env };
@@ -97,6 +97,50 @@ export async function callApi(
97
97
  return json.content[0]?.text ?? '';
98
98
  }
99
99
 
100
+ /**
101
+ * Call the platform-injected AI proxy (routes through IPC to the host process).
102
+ * Available when running inside the Studio sandbox.
103
+ */
104
+ export async function callPlatform(
105
+ systemPrompt: string,
106
+ userPrompt: string,
107
+ model?: string,
108
+ maxTokens?: number,
109
+ ): Promise<string> {
110
+ const provider = (globalThis as any).__fw_llm_provider__;
111
+ if (!provider) throw new Error('Platform AI provider not available');
112
+ const messages = [
113
+ { role: 'system', content: systemPrompt },
114
+ { role: 'user', content: userPrompt },
115
+ ];
116
+ const response = await provider.chat(messages, { model, maxTokens });
117
+ return response.content ?? '';
118
+ }
119
+
120
+ /**
121
+ * Unified AI call that dispatches to the right backend based on provider type.
122
+ */
123
+ export async function callAI(
124
+ pInfo: Pick<ProviderInfo, 'type' | 'apiKey' | 'model' | 'maxTokens'>,
125
+ systemPrompt: string,
126
+ userPrompt: string,
127
+ defaultMaxTokens = 4096,
128
+ ): Promise<string> {
129
+ if (pInfo.type === 'platform') {
130
+ return callPlatform(systemPrompt, userPrompt, pInfo.model, pInfo.maxTokens ?? defaultMaxTokens);
131
+ }
132
+ if (pInfo.type === 'anthropic') {
133
+ return callApi(
134
+ pInfo.apiKey!,
135
+ pInfo.model ?? 'claude-sonnet-4-6',
136
+ pInfo.maxTokens ?? defaultMaxTokens,
137
+ systemPrompt,
138
+ userPrompt,
139
+ );
140
+ }
141
+ return callCli(pInfo.type, systemPrompt + '\n\n' + userPrompt, pInfo.model);
142
+ }
143
+
100
144
  export function parseJsonResponse(text: string): Record<string, unknown> {
101
145
  let cleaned = text.trim();
102
146
  if (cleaned.startsWith('```')) {
package/src/bot/types.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  export type ProviderName = 'anthropic' | 'claude-cli' | 'copilot-cli' | (string & {});
2
2
 
3
3
  export interface ProviderInfo {
4
- type: 'anthropic' | 'claude-cli' | 'copilot-cli';
4
+ type: 'anthropic' | 'claude-cli' | 'copilot-cli' | 'platform';
5
5
  model?: string;
6
6
  maxTokens?: number;
7
7
  apiKey?: string;
@@ -708,16 +708,9 @@ const MANAGED_WORKFLOWS: Record<string, string> = {
708
708
  genesis: 'genesis-task',
709
709
  };
710
710
 
711
- /** Rewrite pack-relative imports to package imports and deduplicate. */
712
- function rewritePackImports(source: string): string {
713
- // Rewrite ../node-types/*.js → package/node-types
714
- // Rewrite ../bot/*.js → package/bot
715
- const rewritten = source
716
- .replace(/from\s+['"]\.\.\/node-types\/[^'"]+['"]/g, "from '@synergenius/flowweaver-pack-weaver/node-types'")
717
- .replace(/from\s+['"]\.\.\/bot\/[^'"]+['"]/g, "from '@synergenius/flowweaver-pack-weaver/bot'");
718
-
719
- // Deduplicate import lines: collapse multiple imports from the same module
720
- const lines = rewritten.split('\n');
711
+ /** Deduplicate import lines: collapse multiple imports from the same module. */
712
+ function deduplicateImports(source: string): string {
713
+ const lines = source.split('\n');
721
714
  const importMap = new Map<string, Set<string>>();
722
715
  const nonImportLines: string[] = [];
723
716
  let pastImports = false;
@@ -748,6 +741,21 @@ function rewritePackImports(source: string): string {
748
741
  return [...dedupedImports, ...nonImportLines].join('\n');
749
742
  }
750
743
 
744
+ /** Rewrite node-type source: ../bot/*.js → package barrel, deduplicate. */
745
+ function rewriteNodeTypeImports(source: string): string {
746
+ const rewritten = source
747
+ .replace(/from\s+['"]\.\.\/bot\/[^'"]+['"]/g, "from '@synergenius/flowweaver-pack-weaver/bot'");
748
+ return deduplicateImports(rewritten);
749
+ }
750
+
751
+ /** Rewrite workflow source: ../node-types/ → ./node-types/ (local ejected), ../bot/ → package barrel. */
752
+ function rewriteWorkflowImports(source: string): string {
753
+ const rewritten = source
754
+ .replace(/from\s+['"]\.\.\/node-types\//g, "from './node-types/")
755
+ .replace(/from\s+['"]\.\.\/bot\/[^'"]+['"]/g, "from '@synergenius/flowweaver-pack-weaver/bot'");
756
+ return deduplicateImports(rewritten);
757
+ }
758
+
751
759
  /** Read a managed workflow source from the pack. */
752
760
  function readPackWorkflowSource(packRoot: URL, workflowBaseName: string): string {
753
761
  const candidates = [
@@ -765,6 +773,180 @@ function readPackWorkflowSource(packRoot: URL, workflowBaseName: string): string
765
773
  throw new Error(`Could not find managed workflow: ${workflowBaseName}`);
766
774
  }
767
775
 
776
+ export interface EjectResult {
777
+ workflow: string;
778
+ file: string;
779
+ nodeTypes: string[];
780
+ botFiles?: string[];
781
+ }
782
+
783
+ /**
784
+ * Read a pack source file, trying src/ then dist/.
785
+ */
786
+ function readPackFile(packRoot: URL, relativePath: string): string {
787
+ const srcCandidate = new URL(`src/${relativePath}`, packRoot);
788
+ const distCandidate = new URL(`dist/${relativePath}`, packRoot);
789
+ try {
790
+ return fs.readFileSync(srcCandidate, 'utf-8');
791
+ } catch {
792
+ return fs.readFileSync(distCandidate, 'utf-8');
793
+ }
794
+ }
795
+
796
+ /**
797
+ * Collect bot utility files transitively imported by node-type files.
798
+ * Traces `from '../bot/<file>.js'` and `from './<file>.js'` within bot/.
799
+ */
800
+ function collectBotDeps(packRoot: URL, ntFiles: string[]): string[] {
801
+ const visited = new Set<string>();
802
+ const queue: string[] = [];
803
+
804
+ // Seed from node-type files: look for ../bot/ imports
805
+ for (const ntFile of ntFiles) {
806
+ let ntSource: string;
807
+ try { ntSource = readPackFile(packRoot, `node-types/${ntFile}`); } catch { continue; }
808
+ const botImportRegex = /from\s+['"]\.\.\/bot\/([^'"]+)['"]/g;
809
+ let m;
810
+ while ((m = botImportRegex.exec(ntSource)) !== null) {
811
+ const botFile = m[1]!.replace(/\.js$/, '.ts');
812
+ if (!visited.has(botFile)) {
813
+ visited.add(botFile);
814
+ queue.push(botFile);
815
+ }
816
+ }
817
+ }
818
+
819
+ // Always include types.ts
820
+ if (!visited.has('types.ts')) {
821
+ visited.add('types.ts');
822
+ queue.push('types.ts');
823
+ }
824
+
825
+ // Trace transitive deps within bot/
826
+ while (queue.length > 0) {
827
+ const botFile = queue.shift()!;
828
+ let botSource: string;
829
+ try { botSource = readPackFile(packRoot, `bot/${botFile}`); } catch { continue; }
830
+ const localImportRegex = /from\s+['"]\.\/([^'"]+)['"]/g;
831
+ let m;
832
+ while ((m = localImportRegex.exec(botSource)) !== null) {
833
+ const dep = m[1]!.replace(/\.js$/, '.ts');
834
+ if (!visited.has(dep)) {
835
+ visited.add(dep);
836
+ queue.push(dep);
837
+ }
838
+ }
839
+ }
840
+
841
+ return [...visited];
842
+ }
843
+
844
+ /**
845
+ * Eject workflow + node-type files into a destination directory.
846
+ *
847
+ * When `standalone` is true, bot utility files are also ejected so the
848
+ * workflows run without the pack installed. Import paths in workflows
849
+ * are rewritten from `../node-types/` to `./node-types/`, and `../bot/`
850
+ * imports in node-type files remain as-is (pointing to the local `bot/`
851
+ * directory).
852
+ *
853
+ * When `standalone` is false (default), bot imports are rewritten to
854
+ * the package barrel (`@synergenius/flowweaver-pack-weaver/bot`).
855
+ */
856
+ export function ejectWorkflows(opts: {
857
+ destDir: string;
858
+ workflows?: string[];
859
+ force?: boolean;
860
+ standalone?: boolean;
861
+ }): EjectResult[] {
862
+ const packRoot = new URL('..', import.meta.url);
863
+ const workflowKeys = opts.workflows ?? Object.keys(MANAGED_WORKFLOWS);
864
+ const standalone = opts.standalone === true;
865
+ const results: EjectResult[] = [];
866
+ const allNtFiles: string[] = [];
867
+
868
+ for (const key of workflowKeys) {
869
+ const baseName = MANAGED_WORKFLOWS[key];
870
+ if (!baseName) continue;
871
+
872
+ const source = readPackWorkflowSource(packRoot, baseName);
873
+
874
+ // In standalone mode, only rewrite ../node-types/ to ./node-types/
875
+ // and keep ../bot/ as-is (local bot/ dir). Otherwise use package barrel.
876
+ let rewritten: string;
877
+ if (standalone) {
878
+ rewritten = source.replace(/from\s+['"]\.\.\/node-types\//g, "from './node-types/");
879
+ } else {
880
+ rewritten = rewriteWorkflowImports(source);
881
+ }
882
+
883
+ const wfFile = `${baseName}.ts`;
884
+ const wfPath = path.join(opts.destDir, wfFile);
885
+
886
+ // Collect node-type files referenced by the workflow
887
+ const ntImportRegex = /from\s+['"]\.\.\/node-types\/([^'"]+)['"]/g;
888
+ const ntFiles: string[] = [];
889
+ let ntMatch;
890
+ while ((ntMatch = ntImportRegex.exec(source)) !== null) {
891
+ const ntFile = ntMatch[1]!.replace(/\.js$/, '.ts');
892
+ if (!ntFiles.includes(ntFile)) ntFiles.push(ntFile);
893
+ if (!allNtFiles.includes(ntFile)) allNtFiles.push(ntFile);
894
+ }
895
+
896
+ // Write workflow (skip if exists and not forced)
897
+ let exists = false;
898
+ try { fs.statSync(wfPath); exists = true; } catch {}
899
+ if (!exists || opts.force) {
900
+ fs.mkdirSync(path.join(opts.destDir, 'node-types'), { recursive: true });
901
+ fs.writeFileSync(wfPath, rewritten, 'utf-8');
902
+
903
+ // Eject each referenced node-type file
904
+ for (const ntFile of ntFiles) {
905
+ const ntSrcCandidates = [
906
+ new URL(`src/node-types/${ntFile}`, packRoot),
907
+ new URL(`dist/node-types/${ntFile}`, packRoot),
908
+ ];
909
+ for (const candidate of ntSrcCandidates) {
910
+ try {
911
+ const ntSource = fs.readFileSync(candidate, 'utf-8');
912
+ // In standalone mode, keep ../bot/ as-is (local). Otherwise rewrite to barrel.
913
+ const ntRewritten = standalone ? ntSource : rewriteNodeTypeImports(ntSource);
914
+ fs.writeFileSync(path.join(opts.destDir, 'node-types', ntFile), ntRewritten, 'utf-8');
915
+ break;
916
+ } catch { /* try next */ }
917
+ }
918
+ }
919
+ }
920
+
921
+ results.push({ workflow: key, file: wfFile, nodeTypes: ntFiles });
922
+ }
923
+
924
+ // In standalone mode, eject bot utility files
925
+ if (standalone) {
926
+ const botFiles = collectBotDeps(packRoot, allNtFiles);
927
+ fs.mkdirSync(path.join(opts.destDir, 'bot'), { recursive: true });
928
+ for (const botFile of botFiles) {
929
+ const destPath = path.join(opts.destDir, 'bot', botFile);
930
+ let exists = false;
931
+ try { fs.statSync(destPath); exists = true; } catch {}
932
+ if (!exists || opts.force) {
933
+ try {
934
+ const botSource = readPackFile(packRoot, `bot/${botFile}`);
935
+ fs.writeFileSync(destPath, botSource, 'utf-8');
936
+ } catch {
937
+ // File not found in pack, skip
938
+ }
939
+ }
940
+ }
941
+ // Attach bot files to results
942
+ for (const r of results) {
943
+ r.botFiles = botFiles;
944
+ }
945
+ }
946
+
947
+ return results;
948
+ }
949
+
768
950
  /**
769
951
  * Resolve a managed workflow path. Checks for a local ejected override first,
770
952
  * then falls back to the pack's own source or dist.
@@ -798,13 +980,6 @@ function resolveWorkflowPath(workflowKey: string, cwd: string): string {
798
980
  }
799
981
 
800
982
  export async function handleEject(opts: ParsedArgs): Promise<void> {
801
- const packRoot = new URL('..', import.meta.url);
802
-
803
- // Determine which workflows to eject
804
- const workflowKeys = opts.ejectWorkflow
805
- ? [opts.ejectWorkflow]
806
- : Object.keys(MANAGED_WORKFLOWS);
807
-
808
983
  if (opts.ejectWorkflow && !MANAGED_WORKFLOWS[opts.ejectWorkflow]) {
809
984
  console.error(`[weaver] Unknown workflow: ${opts.ejectWorkflow}`);
810
985
  console.error(`[weaver] Available: ${Object.keys(MANAGED_WORKFLOWS).join(', ')}`);
@@ -812,29 +987,25 @@ export async function handleEject(opts: ParsedArgs): Promise<void> {
812
987
  return;
813
988
  }
814
989
 
815
- const ejectedFiles: Record<string, string> = {};
990
+ const destDir = process.cwd();
991
+ const workflows = opts.ejectWorkflow ? [opts.ejectWorkflow] : undefined;
816
992
 
817
- for (const key of workflowKeys) {
818
- const baseName = MANAGED_WORKFLOWS[key]!;
819
- let source: string;
820
- try {
821
- source = readPackWorkflowSource(packRoot, baseName);
822
- } catch (err: unknown) {
823
- const msg = err instanceof Error ? err.message : String(err);
824
- console.error(`[weaver] ${msg}`);
825
- process.exit(1);
826
- return;
827
- }
993
+ let results: EjectResult[];
994
+ try {
995
+ results = ejectWorkflows({ destDir, workflows, force: true, standalone: true });
996
+ } catch (err: unknown) {
997
+ const msg = err instanceof Error ? err.message : String(err);
998
+ console.error(`[weaver] ${msg}`);
999
+ process.exit(1);
1000
+ return;
1001
+ }
828
1002
 
829
- const finalSource = rewritePackImports(source);
830
- const fileName = `${baseName}.ts`;
831
- const destPath = path.resolve(process.cwd(), fileName);
832
- fs.writeFileSync(destPath, finalSource, 'utf-8');
833
- ejectedFiles[key] = fileName;
834
- console.log(`[weaver] Ejected ${key} → ${destPath}`);
1003
+ for (const r of results) {
1004
+ console.log(`[weaver] Ejected ${r.workflow} → ${path.resolve(destDir, r.file)} (${r.nodeTypes.length} node types)`);
835
1005
  }
836
1006
 
837
1007
  // Read pack version
1008
+ const packRoot = new URL('..', import.meta.url);
838
1009
  let packVersion = 'unknown';
839
1010
  try {
840
1011
  const pkgPath = new URL('package.json', packRoot);
@@ -843,13 +1014,16 @@ export async function handleEject(opts: ParsedArgs): Promise<void> {
843
1014
  } catch { /* ignore */ }
844
1015
 
845
1016
  // Write/update .weaver-meta.json (merge with existing if present)
846
- const metaPath = path.resolve(process.cwd(), '.weaver-meta.json');
1017
+ const metaPath = path.resolve(destDir, '.weaver-meta.json');
847
1018
  let existingMeta: Record<string, unknown> = {};
848
1019
  try {
849
1020
  existingMeta = JSON.parse(fs.readFileSync(metaPath, 'utf-8'));
850
1021
  } catch { /* start fresh */ }
851
1022
 
852
1023
  const existingWorkflows = (existingMeta.workflowFiles as Record<string, string>) ?? {};
1024
+ const ejectedFiles: Record<string, string> = {};
1025
+ for (const r of results) ejectedFiles[r.workflow] = r.file;
1026
+
853
1027
  const meta = {
854
1028
  ejected: true,
855
1029
  packVersion,
package/src/index.ts CHANGED
@@ -171,3 +171,7 @@ export {
171
171
  genesisUpdateHistory,
172
172
  genesisReport,
173
173
  } from './node-types/index.js';
174
+
175
+ // Eject API (for platform/studio server-side use)
176
+ export { ejectWorkflows } from './cli-handlers.js';
177
+ export type { EjectResult } from './cli-handlers.js';
@@ -39,7 +39,9 @@ export function weaverDetectProvider(projectDir: string, config: WeaverConfig):
39
39
  } else if (providerSetting !== 'auto') {
40
40
  type = providerSetting;
41
41
  } else {
42
- if (process.env.ANTHROPIC_API_KEY) {
42
+ if ((globalThis as any).__fw_llm_provider__) {
43
+ type = 'platform';
44
+ } else if (process.env.ANTHROPIC_API_KEY) {
43
45
  type = 'anthropic';
44
46
  } else if (whichSafe('claude', projectDir)) {
45
47
  type = 'claude-cli';
@@ -2,7 +2,7 @@ import * as fs from 'node:fs';
2
2
  import * as path from 'node:path';
3
3
  import * as os from 'node:os';
4
4
  import type { WeaverContext } from '../bot/types.js';
5
- import { callCli, callApi, parseJsonResponse } from '../bot/ai-client.js';
5
+ import { callAI, parseJsonResponse } from '../bot/ai-client.js';
6
6
  import { executeStep } from '../bot/step-executor.js';
7
7
  import { validateFiles } from '../bot/file-validator.js';
8
8
  import { auditEmit } from '../bot/audit-logger.js';
@@ -103,12 +103,7 @@ export async function weaverExecValidateRetry(
103
103
 
104
104
  const fixPrompt = `The following validation errors occurred:\n${errors}\n\nProvide a fix plan as JSON with steps and summary.`;
105
105
 
106
- let text: string;
107
- if (pInfo.type === 'anthropic') {
108
- text = await callApi(pInfo.apiKey!, pInfo.model ?? 'claude-sonnet-4-6', pInfo.maxTokens ?? 8192, systemPrompt, fixPrompt);
109
- } else {
110
- text = callCli(pInfo.type, systemPrompt + '\n\n' + fixPrompt);
111
- }
106
+ const text = await callAI(pInfo, systemPrompt, fixPrompt, 8192);
112
107
 
113
108
  currentPlan = parseJsonResponse(text);
114
109
  console.log(`\x1b[36m→ Fix plan: ${(currentPlan as { summary?: string }).summary ?? 'generated'}\x1b[0m`);
@@ -1,5 +1,5 @@
1
1
  import type { WeaverEnv, ProviderInfo, WeaverContext } from '../bot/types.js';
2
- import { callCli, callApi, parseJsonResponse } from '../bot/ai-client.js';
2
+ import { callAI, parseJsonResponse } from '../bot/ai-client.js';
3
3
 
4
4
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
5
5
  async function buildWeaverPrompt(): Promise<string> {
@@ -195,18 +195,7 @@ export async function weaverExecuteTarget(
195
195
  : JSON.stringify(req.context, null, 2);
196
196
  const userPrompt = `Context:\n${contextStr}\n\nInstructions:\n${req.prompt}`;
197
197
 
198
- let text: string;
199
- if (pInfo.type === 'anthropic') {
200
- text = await callApi(
201
- pInfo.apiKey!,
202
- pInfo.model ?? 'claude-sonnet-4-6',
203
- pInfo.maxTokens ?? 4096,
204
- systemPrompt,
205
- userPrompt,
206
- );
207
- } else {
208
- text = callCli(pInfo.type, systemPrompt + '\n\n' + userPrompt);
209
- }
198
+ const text = await callAI(pInfo, systemPrompt, userPrompt);
210
199
 
211
200
  return parseJsonResponse(text);
212
201
  },
@@ -1,5 +1,5 @@
1
1
  import type { WeaverEnv } from '../bot/types.js';
2
- import { callCli, callApi, parseJsonResponse } from '../bot/ai-client.js';
2
+ import { callAI, parseJsonResponse } from '../bot/ai-client.js';
3
3
 
4
4
  /**
5
5
  * When validation fails, sends errors + context to the AI and
@@ -50,12 +50,7 @@ export async function weaverFixErrors(
50
50
  const userPrompt = `The following validation errors occurred:\n${errorSummary}\n\nProvide a fix plan as JSON with "steps" and "summary". Each step needs "id", "operation", "description", and "args".`;
51
51
 
52
52
  try {
53
- let text: string;
54
- if (pInfo.type === 'anthropic') {
55
- text = await callApi(pInfo.apiKey!, pInfo.model ?? 'claude-sonnet-4-6', pInfo.maxTokens ?? 8192, systemPrompt, userPrompt);
56
- } else {
57
- text = callCli(pInfo.type, systemPrompt + '\n\n' + userPrompt);
58
- }
53
+ const text = await callAI(pInfo, systemPrompt, userPrompt, 8192);
59
54
 
60
55
  const plan = parseJsonResponse(text);
61
56
  console.log(`\x1b[36m→ Fix plan: ${(plan as { summary?: string }).summary ?? 'generated'}\x1b[0m`);
@@ -1,7 +1,7 @@
1
1
  import * as fs from 'node:fs';
2
2
  import * as path from 'node:path';
3
3
  import type { GenesisConfig, GenesisContext } from '../bot/types.js';
4
- import { callCli, callApi, parseJsonResponse } from '../bot/ai-client.js';
4
+ import { callAI, parseJsonResponse } from '../bot/ai-client.js';
5
5
  import { GenesisStore } from '../bot/genesis-store.js';
6
6
  import { getGenesisSystemPrompt, getOperationExamples } from '../bot/genesis-prompt-context.js';
7
7
 
@@ -126,12 +126,7 @@ async function reviseProposal(
126
126
  'Return the full revised proposal as JSON. Use node IDs and port names that exist in the workflow structure above.',
127
127
  ].join('\n');
128
128
 
129
- let text: string;
130
- if (pInfo.type === 'anthropic') {
131
- text = await callApi(pInfo.apiKey!, pInfo.model ?? 'claude-sonnet-4-6', pInfo.maxTokens ?? 8192, systemPrompt, userPrompt);
132
- } else {
133
- text = callCli(pInfo.type, systemPrompt + '\n\n' + userPrompt);
134
- }
129
+ const text = await callAI(pInfo, systemPrompt, userPrompt, 8192);
135
130
 
136
131
  const parsed = parseJsonResponse(text);
137
132
  return JSON.stringify(parsed);
@@ -1,6 +1,6 @@
1
1
  import * as path from 'node:path';
2
2
  import type { GenesisConfig, GenesisProposal, GenesisContext } from '../bot/types.js';
3
- import { callCli, callApi, parseJsonResponse } from '../bot/ai-client.js';
3
+ import { callAI, parseJsonResponse } from '../bot/ai-client.js';
4
4
  import { getGenesisSystemPrompt, getOperationExamples } from '../bot/genesis-prompt-context.js';
5
5
 
6
6
  /**
@@ -60,18 +60,7 @@ export async function genesisPropose(
60
60
 
61
61
  for (let attempt = 1; attempt <= maxAttempts; attempt++) {
62
62
  try {
63
- let text: string;
64
- if (pInfo.type === 'anthropic') {
65
- text = await callApi(
66
- pInfo.apiKey!,
67
- pInfo.model ?? 'claude-sonnet-4-6',
68
- pInfo.maxTokens ?? 8192,
69
- systemPrompt,
70
- userPrompt,
71
- );
72
- } else {
73
- text = callCli(pInfo.type, systemPrompt + '\n\n' + userPrompt, pInfo.model);
74
- }
63
+ const text = await callAI(pInfo, systemPrompt, userPrompt, 8192);
75
64
 
76
65
  const proposal = parseJsonResponse(text) as unknown as GenesisProposal;
77
66
  console.log(`\x1b[36m→ Proposal: ${proposal.summary} (${proposal.operations.length} ops, impact=${proposal.impactLevel})\x1b[0m`);
@@ -1,5 +1,5 @@
1
1
  import type { WeaverContext } from '../bot/types.js';
2
- import { callCli, callApi, parseJsonResponse } from '../bot/ai-client.js';
2
+ import { callAI, parseJsonResponse } from '../bot/ai-client.js';
3
3
  import { auditEmit } from '../bot/audit-logger.js';
4
4
 
5
5
  /**
@@ -49,18 +49,7 @@ export async function weaverPlanTask(
49
49
  const userPrompt = `Task: ${task.instruction}\nMode: ${task.mode ?? 'create'}\n${task.targets ? 'Targets: ' + task.targets.join(', ') : ''}\n\nPlan this task. Return a JSON plan with steps and summary.`;
50
50
 
51
51
  try {
52
- let text: string;
53
- if (pInfo.type === 'anthropic') {
54
- text = await callApi(
55
- pInfo.apiKey!,
56
- pInfo.model ?? 'claude-sonnet-4-6',
57
- pInfo.maxTokens ?? 8192,
58
- systemPrompt,
59
- userPrompt,
60
- );
61
- } else {
62
- text = callCli(pInfo.type, systemPrompt + '\n\n' + userPrompt);
63
- }
52
+ const text = await callAI(pInfo, systemPrompt, userPrompt, 8192);
64
53
 
65
54
  const plan = parseJsonResponse(text);
66
55
  console.log(`\x1b[36m→ Plan: ${(plan as { summary?: string }).summary ?? 'generated'}\x1b[0m`);