Compare commits

...

47 Commits

Author SHA1 Message Date
github-actions[bot]
8806f4c2f4 chore(release): Update version to v1.4.373 2026-01-04 21:08:00 +00:00
Kayvan Sylvan
b381bae24a Merge pull request #1915 from majiayu000/fix-1842-feature-request-parallelize-au-0101-2335
feat: parallelize audio chunk transcription for improved performance
2026-01-04 13:04:56 -08:00
Kayvan Sylvan
a6c753499b chore: incoming 1915 changelog entry 2026-01-04 13:01:42 -08:00
Kayvan Sylvan
90b2975fba Merge pull request #1914 from majiayu000/fix-1869-feature-request-make-codehelpe-0101-2323
feat(code_helper): add stdin support for piping file lists
2026-01-04 12:55:06 -08:00
Kayvan Sylvan
145499ee4c chore: incoming 1914 changelog entry 2026-01-04 12:51:06 -08:00
Kayvan Sylvan
f9359c99dc Merge branch 'main' into fix-1869-feature-request-make-codehelpe-0101-2323 2026-01-04 12:48:30 -08:00
github-actions[bot]
6b6d0adbfb chore(release): Update version to v1.4.372 2026-01-04 20:15:36 +00:00
Kayvan Sylvan
55c94e65da Merge pull request #1913 from majiayu000/fix-1910-bug-rest-api-chat-endpoint-doe-0101-2307
fix: REST API /chat endpoint doesn't pass 'search' parameter to ChatOptions
2026-01-04 12:12:54 -08:00
Kayvan Sylvan
2118013547 chore: incoming 1913 changelog entry 2026-01-04 12:05:33 -08:00
Kayvan Sylvan
82a9f02879 Merge branch 'main' into fix-1910-bug-rest-api-chat-endpoint-doe-0101-2307 2026-01-04 12:05:04 -08:00
github-actions[bot]
602304e417 chore(release): Update version to v1.4.371 2026-01-04 19:25:44 +00:00
Kayvan Sylvan
c0d00aeb1f Merge pull request #1923 from ksylvan/kayvan/fix-generate-changelog-db-sync-issues
ChangeLog Generation stability
2026-01-04 11:22:43 -08:00
Kayvan Sylvan
1ec8ecba24 chore: format fix 2026-01-04 11:16:18 -08:00
Kayvan Sylvan
ad1465a2e5 chore: incoming 1923 changelog entry 2026-01-04 11:13:03 -08:00
Kayvan Sylvan
12b6cf4a0a fix: improve date parsing and prevent early return when PR numbers exist
## CHANGES

- Add SQLite datetime formats to version date parsing logic
- Loop through multiple date formats until one succeeds
- Include SQLite fractional seconds format support
- Prevent early return when version has PR numbers to output
- Simplify error handling for date parsing failures
2026-01-04 11:07:49 -08:00
github-actions[bot]
a6ad1d77f9 chore(release): Update version to v1.4.370 2026-01-04 17:19:48 +00:00
Kayvan Sylvan
af3403ae44 Merge pull request #1921 from ksylvan/kayvan/fix-for-earliest-pr-merge-time
chore: remove redundant `--sync-db` step from changelog workflow
2026-01-04 09:17:11 -08:00
Kayvan Sylvan
c971781072 chore: incoming 1921 changelog entry 2026-01-04 09:10:58 -08:00
Kayvan Sylvan
fd0ac8aa3b feat: clean up heal_person pattern by removing duplicate content
## CHANGES

- Remove duplicate IDENTITY and PURPOSE section from input block
- Remove redundant STEPS instructions from template
- Remove duplicate OUTPUT INSTRUCTIONS from pattern file
- Simplify INPUT section to single placeholder
- Clean up unnecessary whitespace and formatting
2026-01-04 09:09:35 -08:00
Kayvan Sylvan
0991c52e6f chore: remove redundant --sync-db step from changelog workflow
- Remove duplicate database sync command from version workflow
- Simplify changelog generation to single process-prs step
- Keep database file staging after changelog generation
2026-01-04 08:45:36 -08:00
github-actions[bot]
c60e8d1bf7 chore(release): Update version to v1.4.369 2026-01-04 07:33:42 +00:00
Kayvan Sylvan
a5ac60cedf Merge pull request #1919 from ksylvan/kayvan/one-more-ci-cd-changelog-fix
Fix the `last_pr_sync` setting during PR incoming processing
2026-01-03 23:30:54 -08:00
Kayvan Sylvan
96ce0838b5 chore: incoming 1919 changelog entry 2026-01-03 23:28:42 -08:00
Kayvan Sylvan
3d88f8e2fc fix: update SetLastPRSync to use version date instead of current time
- Change last_pr_sync to use versionDate instead of time.Now()
- Ensure future runs fetch PRs merged after the version date
- Add clarifying comments explaining the sync timing logic
2026-01-03 23:23:19 -08:00
github-actions[bot]
f588af0887 chore(release): Update version to v1.4.368 2026-01-04 06:55:29 +00:00
Kayvan Sylvan
c4bca7a302 Merge pull request #1918 from ksylvan/kayvan/fix-changelog-generation
Maintenance: Fix  ChangeLog Generation during CI/CD
2026-01-03 22:51:45 -08:00
Kayvan Sylvan
1ced245bfe chore: incoming 1918 changelog entry 2026-01-03 22:41:59 -08:00
Kayvan Sylvan
d6100026da chore: update cache metadata before staging release changes
- Add cache metadata update step before staging release changes
- Set last_processed_tag to current version being processed
- Update last_pr_sync timestamp to current time
- Include warning messages for failed metadata updates
- Ensure metadata commits alongside other release changes
2026-01-03 22:34:11 -08:00
Kayvan Sylvan
fd465d4130 docs: refactor CHANGELOG.md entries with improved formatting and conventional commit prefixes
- Consolidate git worktree fixes into single PR #1917 entry
- Standardize commit message prefixes using conventional commits format
- Rewrite bullet points in imperative mood throughout changelog
- Condense verbose multi-line entries into concise single bullets
- Reorder PR entries chronologically within version sections
- Remove redundant Co-Authored-By attribution lines from entries
- Fix inconsistent date formats in version headers
- Simplify dependency update descriptions to essential information
- Update changelog database binary with new entry formatting
2026-01-03 22:27:18 -08:00
Kayvan Sylvan
0776e77872 Merge branch 'main' into fix-1910-bug-rest-api-chat-endpoint-doe-0101-2307 2026-01-03 17:09:28 -08:00
Kayvan Sylvan
cb2759a5a1 Merge branch 'main' into fix-1842-feature-request-parallelize-au-0101-2335 2026-01-03 17:05:14 -08:00
Kayvan Sylvan
c32a650eaa Merge branch 'main' into fix-1869-feature-request-make-codehelpe-0101-2323 2026-01-03 17:03:59 -08:00
github-actions[bot]
b41aa2dbdc chore(release): Update version to v1.4.367 2026-01-03 22:53:06 +00:00
Kayvan Sylvan
21ec2ca9d9 Merge pull request #1912 from berniegreen/feature/metadata-refactor
refactor: implement structured streaming and metadata support
2026-01-03 14:50:15 -08:00
Kayvan Sylvan
672b920a89 chore: incoming 1912 changelog entry 2026-01-03 14:29:04 -08:00
majiayu000
8a28ca7b1e feat: parallelize audio chunk transcription using goroutines
Signed-off-by: majiayu000 <1835304752@qq.com>
2026-01-01 23:38:32 +08:00
majiayu000
435d61ae0e feat(code_helper): add stdin support for piping file lists
Add ability to pipe file lists to code_helper via stdin, enabling
use cases like:
  find . -name '*.go' | code_helper "instructions"
  git ls-files '*.py' | code_helper "Add type hints"

The tool now detects if stdin is a pipe and accepts a single argument
(instructions) in that mode, reading file paths from stdin line by line.

Backward compatible with existing directory scanning mode.

Signed-off-by: majiayu000 <1835304752@qq.com>
2026-01-01 23:28:11 +08:00
majiayu000
6ea5551f06 fix: pass Search and SearchLocation parameters to ChatOptions in /chat endpoint
Signed-off-by: majiayu000 <1835304752@qq.com>
2026-01-01 23:09:30 +08:00
berniegreen
b04346008b fix: add missing newline to end of chatter_test.go 2025-12-31 16:59:30 -06:00
berniegreen
c7ecac3262 test: add test for metadata stream propagation 2025-12-31 15:56:20 -06:00
berniegreen
07457d86d3 docs: document --show-metadata flag in README 2025-12-31 15:15:15 -06:00
berniegreen
8166ee7a18 docs: update swagger documentation and fix dryrun tests 2025-12-31 15:13:20 -06:00
berniegreen
c539b1edfc feat: implement REST API support for metadata streaming (Phase 5) 2025-12-31 12:43:48 -06:00
berniegreen
66d3bf786e feat: implement CLI support for metadata display (Phase 4) 2025-12-31 12:41:06 -06:00
berniegreen
569f50179d refactor: implement structured streaming in all AI vendors (Phase 3) 2025-12-31 12:38:38 -06:00
berniegreen
477ca045b0 refactor: update Vendor interface and Chatter for structured streaming (Phase 2) 2025-12-31 12:26:13 -06:00
berniegreen
e40d51cc71 feat: add domain types for structured streaming (Phase 1) 2025-12-31 12:19:27 -06:00
37 changed files with 1047 additions and 370 deletions

View File

@@ -94,7 +94,6 @@ jobs:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
go run ./cmd/generate_changelog --process-prs ${{ steps.increment_version.outputs.new_tag }}
go run ./cmd/generate_changelog --sync-db
git add ./cmd/generate_changelog/changelog.db
- name: Commit changes
run: |

View File

@@ -1,59 +1,111 @@
# Changelog
## v1.4.366 (2025-12-31)
## v1.4.373 (2026-01-04)
### PR [#1914](https://github.com/danielmiessler/Fabric/pull/1914) by [majiayu000](https://github.com/majiayu000): feat(code_helper): add stdin support for piping file lists
- Added stdin support for piping file lists to code_helper, enabling commands like `find . -name '*.go' | code_helper "instructions"` and `git ls-files '*.py' | code_helper "Add type hints"`
- Implemented automatic detection of stdin pipe mode with single argument (instructions) support
- Enhanced tool to read file paths from stdin line by line while maintaining backward compatibility with existing directory scanning functionality
### PR [#1915](https://github.com/danielmiessler/Fabric/pull/1915) by [majiayu000](https://github.com/majiayu000): feat: parallelize audio chunk transcription for improved performance
- Parallelize audio chunk transcription using goroutines for improved performance
## v1.4.372 (2026-01-04)
### PR [#1913](https://github.com/danielmiessler/Fabric/pull/1913) by [majiayu000](https://github.com/majiayu000): fix: REST API /chat endpoint doesn't pass 'search' parameter to ChatOptions
- Fix: REST API /chat endpoint now properly passes Search and SearchLocation parameters to ChatOptions
## v1.4.371 (2026-01-04)
### PR [#1923](https://github.com/danielmiessler/Fabric/pull/1923) by [ksylvan](https://github.com/ksylvan): ChangeLog Generation stability
- Fix: improve date parsing and prevent early return when PR numbers exist
- Add SQLite datetime formats to version date parsing logic
- Loop through multiple date formats until one succeeds
- Include SQLite fractional seconds format support
- Prevent early return when version has PR numbers to output
## v1.4.370 (2026-01-04)
### PR [#1921](https://github.com/danielmiessler/Fabric/pull/1921) by [ksylvan](https://github.com/ksylvan): chore: remove redundant `--sync-db` step from changelog workflow
- Remove redundant `--sync-db` step from changelog workflow
- Remove duplicate database sync command from version workflow
- Simplify changelog generation to single process-prs step
- Clean up `heal_person` pattern by removing duplicate content sections
- Remove duplicate IDENTITY, PURPOSE, STEPS, and OUTPUT INSTRUCTIONS from pattern file
## v1.4.369 (2026-01-04)
### PR [#1919](https://github.com/danielmiessler/Fabric/pull/1919) by [ksylvan](https://github.com/ksylvan): Fix the `last_pr_sync` setting during PR incoming processing
- Fix: update `SetLastPRSync` to use version date instead of current time
- Change last_pr_sync to use versionDate instead of time.Now()
- Ensure future runs fetch PRs merged after the version date
- Add clarifying comments explaining the sync timing logic
## v1.4.368 (2026-01-04)
### PR [#1918](https://github.com/danielmiessler/Fabric/pull/1918) by [ksylvan](https://github.com/ksylvan): Maintenance: Fix ChangeLog Generation during CI/CD
- Refactor CHANGELOG.md entries with improved formatting and conventional commit prefixes
- Consolidate git worktree fixes into single PR #1917 entry
- Reorder PR entries chronologically within version sections
- Add cache metadata update step before staging release changes
- Update changelog database binary with new entry formatting
## v1.4.367 (2026-01-03)
### PR [#1912](https://github.com/danielmiessler/Fabric/pull/1912) by [berniegreen](https://github.com/berniegreen): refactor: implement structured streaming and metadata support
- Feat: add domain types for structured streaming (Phase 1)
- Refactor: update Vendor interface and Chatter for structured streaming (Phase 2)
- Refactor: implement structured streaming in all AI vendors (Phase 3)
- Feat: implement CLI support for metadata display (Phase 4)
- Feat: implement REST API support for metadata streaming (Phase 5)
## v1.4.366 (2026-01-03)
### PR [#1917](https://github.com/danielmiessler/Fabric/pull/1917) by [ksylvan](https://github.com/ksylvan): Fix: generate_changelog now works in Git Work Trees
- Fix: improve git worktree status detection to ignore staged-only files and check worktree status codes instead of using IsClean method
- Fix: use native git CLI for add/commit in worktrees to resolve go-git issues with shared object databases
- Check filesystem existence of staged files to handle worktree scenarios and ignore files staged in main repo that don't exist in worktree
- Update GetStatusDetails to only include worktree-modified files and ignore unmodified and untracked files in clean check
- Allow staged files that exist in worktree to be committed normally and fix 'cannot create empty commit: clean working tree' errors
### PR [#1909](https://github.com/danielmiessler/Fabric/pull/1909) by [copyleftdev](https://github.com/copyleftdev): feat: add greybeard_secure_prompt_engineer pattern
- Added greybeard_secure_prompt_engineer pattern
- Updated changelog with incoming entry
- Feat: add greybeard_secure_prompt_engineer pattern
### Direct commits
- Fix: use native git CLI for add/commit in worktrees
go-git has issues with worktrees where the object database isn't properly
shared, causing 'invalid object' errors when trying to commit. Switching
to native git CLI for add and commit operations resolves this.
This fixes generate_changelog failing in worktrees with errors like:
- 'cannot create empty commit: clean working tree'
- 'error: invalid object ... Error building trees'
Co-Authored-By: Warp <agent@warp.dev>
- Fix: IsWorkingDirectoryClean to work correctly in worktrees
- Check filesystem existence of staged files to handle worktree scenarios
- Ignore files staged in main repo that don't exist in worktree
- Allow staged files that exist in worktree to be committed normally
Co-Authored-By: Warp <agent@warp.dev>
- Fix: improve git worktree status detection to ignore staged-only files
- Add worktree-specific check for actual working directory changes
- Filter out files that are only staged but not in worktree
- Check worktree status codes instead of using IsClean method
- Update GetStatusDetails to only include worktree-modified files
- Ignore unmodified and untracked files in clean check
- Feat: implement REST API support for metadata streaming (Phase 5)
- Feat: implement CLI support for metadata display (Phase 4)
- Refactor: implement structured streaming in all AI vendors (Phase 3)
## v1.4.365 (2025-12-30)
### PR [#1908](https://github.com/danielmiessler/Fabric/pull/1908) by [rodaddy](https://github.com/rodaddy): feat(ai): add VertexAI provider for Claude models
- Added support for Google Cloud Vertex AI as a provider to access Claude models using Application Default Credentials (ADC)
- Enabled routing of Fabric requests through Google Cloud Platform instead of directly to Anthropic for GCP billing
- Implemented support for Claude models (Sonnet 4.5, Opus 4.5, Haiku 4.5, etc.) via Vertex AI
- Added Google ADC authentication support eliminating the need for API keys
- Configured project ID and region settings with 'global' as default for cost optimization
- Add support for Google Cloud Vertex AI as a provider to access Claude models using Application Default Credentials (ADC)
- Enable routing of Fabric requests through Google Cloud Platform instead of directly to Anthropic for GCP billing
- Support for Claude models (Sonnet 4.5, Opus 4.5, Haiku 4.5, etc.) via Vertex AI with configurable project ID and region
- Implement full streaming and non-streaming request capabilities with complete ai.Vendor interface
- Extract message conversion logic to dedicated `toMessages` helper method with proper role handling and validation
## v1.4.364 (2025-12-28)
### PR [#1907](https://github.com/danielmiessler/Fabric/pull/1907) by [majiayu000](https://github.com/majiayu000): feat(gui): add Session Name support for multi-turn conversations
- Added Session Name support for multi-turn conversations in GUI chat interface, enabling persistent conversations similar to CLI's --session flag
- Added SessionName field to PromptRequest and sessionName to ChatPrompt interface for proper session handling
- Extracted SessionSelector component with Select component instead of native dropdown for better user experience
- Implemented session message loading when selecting existing sessions with proper error handling
- Fixed Select component binding and empty input handling to prevent redundant API calls and properly clear sessions
- Add Session Name support for multi-turn conversations in GUI chat interface, enabling persistent conversations similar to CLI's --session flag
- Extract session UI into dedicated SessionSelector component with proper Select component integration
- Add session message loading functionality when selecting existing sessions
- Fix session input handling to prevent resetting on each keystroke and improve layout with vertical stacking
- Implement proper error handling for session loading and two-way binding with Select component
## v1.4.363 (2025-12-25)
@@ -69,27 +121,31 @@ Co-Authored-By: Warp <agent@warp.dev>
### PR [#1904](https://github.com/danielmiessler/Fabric/pull/1904) by [majiayu000](https://github.com/majiayu000): fix: resolve WebUI tooltips not rendering due to overflow clipping
- Fix: resolve WebUI tooltips not rendering due to overflow clipping by using position: fixed and getBoundingClientRect() to calculate tooltip position dynamically, preventing tooltips from being clipped by parent containers with overflow: hidden
- Refactor: extract tooltip positioning logic into separate positioning.ts module for better code organization and maintainability
- Improve accessibility with aria-describedby attributes and unique IDs for better screen reader support
- Add reactive tooltip position updates on scroll and resize events for dynamic positioning
- Add SSR safety with isBrowser flag check and comprehensive unit test coverage for the positioning functions
- Fix WebUI tooltips not rendering due to overflow clipping by using position: fixed and getBoundingClientRect() for dynamic positioning
- Extract positioning calculations into dedicated `positioning.ts` module for better code organization
- Add reactive tooltip position updates on scroll and resize events for improved user experience
- Improve accessibility with `aria-describedby` attributes and unique IDs for better screen reader support
- Update unit tests to use extracted functions and add test coverage for style formatting function
## v1.4.361 (2025-12-25)
### PR [#1905](https://github.com/danielmiessler/Fabric/pull/1905) by [majiayu000](https://github.com/majiayu000): fix: optimize oversized logo images reducing package size by 93%
- Optimize oversized logo images reducing package size by 93%
- Fix: optimize oversized logo images reducing package size by 93%
- Replace 42MB favicon.png with proper 64x64 PNG (4.7KB)
- Replace 42MB fabric-logo.png with static PNG from first GIF frame (387KB)
- Optimize animated GIF from 42MB to 5.4MB (half resolution, 12fps, 128 colors)
- Update docs/images/fabric-logo-gif.gif with optimized version
- Chore: incoming 1905 changelog entry
### Direct commits
- Fix: resolve WebUI tooltips not rendering due to overflow clipping
## v1.4.360 (2025-12-23)
### PR [#1903](https://github.com/danielmiessler/Fabric/pull/1903) by [ksylvan](https://github.com/ksylvan): Update project dependencies and core SDK versions
- Update project dependencies and core SDK versions
- Chore: update project dependencies and core SDK versions
- Upgrade AWS SDK v2 components to latest stable versions
- Update Ollama library to version 0.13.5 for improvements
- Bump Google API and GenAI dependencies to newer releases
@@ -100,56 +156,50 @@ Co-Authored-By: Warp <agent@warp.dev>
### PR [#1902](https://github.com/danielmiessler/Fabric/pull/1902) by [ksylvan](https://github.com/ksylvan): Code Cleanup and Simplification
- Chore: simplify error formatting and clean up model assignment logic
- Remove redundant fmt.Sprintf calls from error formatting logic
- Simplify model assignment to always use normalized model names
- Remove unused variadic parameter from the VendorsManager Clear method
- Chore: incoming 1902 changelog entry
## v1.4.358 (2025-12-23)
### PR [#1901](https://github.com/danielmiessler/Fabric/pull/1901) by [orbisai0security](https://github.com/orbisai0security): sexurity fix: Ollama update: CVE-2025-63389
- Fix: resolve critical vulnerability CVE-2025-63389 (update Ollama Go library)
- Chore: incoming 1901 changelog entry
- Fix: resolve critical vulnerability CVE-2025-63389
## v1.4.357 (2025-12-22)
### PR [#1897](https://github.com/danielmiessler/Fabric/pull/1897) by [ksylvan](https://github.com/ksylvan): feat: add MiniMax provider support to OpenAI compatible plugin
- Add MiniMax provider support to OpenAI compatible plugin
- Add MiniMax provider configuration to ProviderMap
- Set MiniMax base URL to api.minimaxi.com/v1
- Configure MiniMax with ImplementsResponses as false
- Add test case for MiniMax provider validation
- Add MiniMax provider configuration to ProviderMap with base URL set to api.minimaxi.com/v1
- Configure MiniMax with ImplementsResponses as false and add test case for provider validation
### Direct commits
- Docs: add v1.4.356 release note highlighting complete i18n support
- Add v1.4.356 entry to Recent Major Features list
- Highlight full setup prompt i18n across 10 languages
- Note intelligent environment variable handling for consistency
- Add v1.4.356 release note highlighting complete internationalization support across 10 languages
- Highlight full setup prompt i18n and intelligent environment variable handling for consistency
## v1.4.356 (2025-12-22)
### PR [#1895](https://github.com/danielmiessler/Fabric/pull/1895) by [ksylvan](https://github.com/ksylvan): Localize setup process and add funding configuration
- Localize setup prompts and error messages across multiple languages
- Implement helper for localized questions with static environment keys
- Update environment variable builder to handle hyphenated plugin names
- Replace hardcoded console output with localized i18n translation strings
- Add GitHub and Buy Me a Coffee funding configuration
- Localize setup prompts and error messages across multiple languages for improved user experience
- Add GitHub and Buy Me a Coffee funding configuration to support project development
- Implement helper for localized questions with static environment keys to streamline internationalization
- Update environment variable builder to handle hyphenated plugin names properly
- Replace hardcoded console output with localized i18n translation strings throughout the application
## v1.4.355 (2025-12-20)
### PR [#1890](https://github.com/danielmiessler/Fabric/pull/1890) by [ksylvan](https://github.com/ksylvan): Bundle yt-dlp with fabric in Nix flake, introduce slim variant
- Added yt-dlp bundling with fabric package and introduced fabric-slim variant
- Renamed original fabric package to fabricSlim and created new fabric package as symlinkJoin of fabricSlim and yt-dlp
- Added fabric-slim output for the slim variant and updated default package to point to bundled fabric
- Enhanced fabric meta description to note yt-dlp inclusion and set mainProgram to fabric in bundled package
- Added wrapper for fabric binary to include PATH in execution environment
- Added bundled yt-dlp with fabric package in Nix flake configuration
- Introduced fabric-slim variant as a lightweight alternative without yt-dlp
- Renamed original fabric package to fabricSlim for better organization
- Created new fabric package as symlinkJoin of fabricSlim and yt-dlp
- Updated default package to point to the bundled fabric version with yt-dlp
## v1.4.354 (2025-12-19)
@@ -166,7 +216,8 @@ Co-Authored-By: Warp <agent@warp.dev>
### PR [#1887](https://github.com/danielmiessler/Fabric/pull/1887) by [bvandevliet](https://github.com/bvandevliet): feat: correct video title and added description to yt transcript api response
- Feat: correct video title (instead of id) and added description to yt transcript api response
- Updated API documentation.
- Updated API documentation
- Chore: incoming 1887 changelog entry
## v1.4.352 (2025-12-18)
@@ -187,9 +238,18 @@ Co-Authored-By: Warp <agent@warp.dev>
### PR [#1882](https://github.com/danielmiessler/Fabric/pull/1882) by [bvandevliet](https://github.com/bvandevliet): Added yt-dlp package to docker image
- Added yt-dlp package to docker image.
- Chore: incoming 1882 changelog entry
## v1.4.350 (2025-12-18)
### PR [#1884](https://github.com/danielmiessler/Fabric/pull/1884) by [ksylvan](https://github.com/ksylvan): Implement interactive Swagger API documentation and automated OpenAPI specification generation
- Add Swagger UI at `/swagger/index.html` endpoint
- Generate OpenAPI spec files (JSON and YAML)
- Document chat, patterns, and models endpoints
- Update contributing guide with Swagger annotation instructions
- Add swaggo dependencies to project
### PR [#1880](https://github.com/danielmiessler/Fabric/pull/1880) by [ksylvan](https://github.com/ksylvan): docs: add REST API server section and new endpoint reference
- Add README table-of-contents link for REST API
@@ -198,52 +258,44 @@ Co-Authored-By: Warp <agent@warp.dev>
- Describe sessions management and model listing endpoints
- Provide curl examples for key API workflows
### PR [#1884](https://github.com/danielmiessler/Fabric/pull/1884) by [ksylvan](https://github.com/ksylvan): Implement interactive Swagger API documentation and automated OpenAPI specification generation
- Add Swagger UI at `/swagger/index.html` endpoint
- Generate OpenAPI spec files (JSON and YAML)
- Document chat, patterns, and models endpoints
- Update contributing guide with Swagger annotation instructions
- Configure authentication bypass for Swagger documentation
## v1.4.349 (2025-12-16)
### PR [#1877](https://github.com/danielmiessler/Fabric/pull/1877) by [ksylvan](https://github.com/ksylvan): modernize: update GitHub Actions and modernize Go code
- Modernize GitHub Actions and Go code with latest stdlib features
- Upgrade GitHub Actions to latest versions (v6, v21) and add modernization check step
- Modernize: update GitHub Actions and modernize Go code with latest stdlib features
- Upgrade GitHub Actions to latest versions (v6, v21)
- Add modernization check step in CI workflow
- Replace strings manipulation with `strings.CutPrefix` and `strings.CutSuffix`
- Replace manual loops with `slices.Contains` for validation and use `strings.SplitSeq` for iterator-based splitting
- Replace `fmt.Sprintf` with `fmt.Appendf` for efficiency and simplify padding calculation with `max` builtin
- Replace manual loops with `slices.Contains` for validation
## v1.4.348 (2025-12-16)
### PR [#1876](https://github.com/danielmiessler/Fabric/pull/1876) by [ksylvan](https://github.com/ksylvan): modernize Go code with TypeFor and range loops
- Replace reflect.TypeOf with TypeFor generic syntax for improved type handling
- Convert traditional for loops to range-based iterations for better code readability
- Simplify reflection usage in CLI flag handling to reduce complexity
- Update test loops to use range over integers for cleaner test code
- Refactor string processing loops in template plugin to use modern Go patterns
- Replace reflect.TypeOf with TypeFor generic syntax for improved type safety
- Convert traditional for loops to range-based iterations for cleaner code
- Simplify reflection usage in CLI flag handling
- Update test loops to use range over integers
- Refactor string processing loops in template plugin
## v1.4.347 (2025-12-16)
### PR [#1875](https://github.com/danielmiessler/Fabric/pull/1875) by [ksylvan](https://github.com/ksylvan): modernize: update benchmarks to use b.Loop and refactor map copying
- Updated benchmark loops to use cleaner `b.Loop()` syntax
- Removed unnecessary `b.ResetTimer()` call in token benchmark
- Used `maps.Copy` for merging variables in patterns handler
- Update benchmark loops to use cleaner `b.Loop()` syntax
- Remove unnecessary `b.ResetTimer()` call in token benchmark
- Use `maps.Copy` for merging variables in patterns handler
- Update benchmarks to use b.Loop and refactor map copying
## v1.4.346 (2025-12-16)
### PR [#1874](https://github.com/danielmiessler/Fabric/pull/1874) by [ksylvan](https://github.com/ksylvan): refactor: replace interface{} with any across codebase
- Part 1 of dealing with #1873 as pointed out by @philoserf
- Replace `interface{}` with `any` in slice type declarations throughout the codebase
- Update map types from `map[string]interface{}` to `map[string]any` for modern Go standards
- Replace `interface{}` with `any` in slice type declarations
- Update map types from `map[string]interface{}` to `map[string]any`
- Change variadic function parameters to use `...any` instead of `...interface{}`
- Modernize JSON unmarshaling variables to use `any` for consistency
- Update struct fields and method signatures to prefer the `any` alias over legacy interface syntax
- Modernize JSON unmarshaling variables to `any` for consistency
- Update struct fields and method signatures to prefer `any` alias
## v1.4.345 (2025-12-15)
@@ -261,12 +313,18 @@ Co-Authored-By: Warp <agent@warp.dev>
- Chore: update flake
- Merge branch 'main' into update-flake
- Chore: incoming 1867 changelog entry
## v1.4.343 (2025-12-14)
### PR [#1829](https://github.com/danielmiessler/Fabric/pull/1829) by [dependabo](https://github.com/apps/dependabot): chore(deps): bump js-yaml from 4.1.0 to 4.1.1 in /web in the npm_and_yarn group across 1 directory
### PR [#1829](https://github.com/danielmiessler/Fabric/pull/1829) by [dependabot[bot]](https://github.com/apps/dependabot): chore(deps): bump js-yaml from 4.1.0 to 4.1.1 in /web in the npm_and_yarn group across 1 directory
- Updated js-yaml dependency from version 4.1.0 to 4.1.1 in the /web directory
- Updated js-yaml dependency from version 4.1.0 to 4.1.1 in the web directory
- Added changelog entry for incoming PR #1829
### Direct commits
- Updated flake configuration
## v1.4.342 (2025-12-13)
@@ -278,7 +336,7 @@ Co-Authored-By: Warp <agent@warp.dev>
- Add os import to support stderr error writes
- Preserve help-output suppression and exit behavior
## v1.4.341 (2025-12-10)
## v1.4.341 (2025-12-11)
### PR [#1860](https://github.com/danielmiessler/Fabric/pull/1860) by [ksylvan](https://github.com/ksylvan): fix: allow resetting required settings without validation errors
@@ -292,19 +350,19 @@ Co-Authored-By: Warp <agent@warp.dev>
### PR [#1856](https://github.com/danielmiessler/Fabric/pull/1856) by [ksylvan](https://github.com/ksylvan): Add support for new ClaudeHaiku 4.5 models
- Add support for new ClaudeHaiku models in client
- Add `ModelClaudeHaiku4_5` to supported models
- Add `ModelClaudeHaiku4_5_20251001` to supported models
- Added support for new ClaudeHaiku 4.5 models in client
- Added `ModelClaudeHaiku4_5` to supported models list
- Added `ModelClaudeHaiku4_5_20251001` to supported models list
## v1.4.339 (2025-12-08)
### PR [#1855](https://github.com/danielmiessler/Fabric/pull/1855) by [ksylvan](https://github.com/ksylvan): feat: add image attachment support for Ollama vision models
- Add multi-modal image support to Ollama client
- Add base64 and io imports for image handling
- Store httpClient separately in Client struct for reuse
- Convert createChatRequest to return error for validation
- Implement convertMessage to handle multi-content chat messages
- Add loadImageBytes to fetch images from URLs
- Support base64 data URLs for inline images
- Handle HTTP image URLs with context propagation
## v1.4.338 (2025-12-04)
@@ -331,21 +389,17 @@ Co-Authored-By: Warp <agent@warp.dev>
### PR [#1848](https://github.com/danielmiessler/Fabric/pull/1848) by [zeddy303](https://github.com/zeddy303): Fix localStorage SSR error in favorites-store
- Fix localStorage SSR error in favorites-store by using SvelteKit's browser constant instead of typeof localStorage check to properly handle server-side rendering and prevent 'localStorage.getItem is not a function' error when running dev server
- Add changelog entry for incoming PR #1848
## v1.4.335 (2025-11-28)
### PR [#1847](https://github.com/danielmiessler/Fabric/pull/1847) by [ksylvan](https://github.com/ksylvan): Improve model name matching for NeedsRaw in Ollama plugin
- Improved model name matching in Ollama plugin by replacing prefix-based matching with substring matching
- Enhanced NeedsRaw functionality to support more flexible model name detection
- Improved model name matching in Ollama plugin by replacing prefix matching with substring matching
- Enhanced Ollama model name detection by enabling substring-based search instead of prefix-only matching
- Added "conceptmap" to VSCode dictionary settings for better development experience
- Fixed typo in README documentation
- Renamed `ollamaPrefixes` variable to `ollamaSearchStrings` for better code clarity
- Replaced `HasPrefix` function with `Contains` for more comprehensive model matching
- Added "conceptmap" to VSCode dictionary settings
### Direct commits
- Merge branch 'danielmiessler:main' into main
- Docs: Fix typo in README
## v1.4.334 (2025-11-26)
@@ -359,10 +413,6 @@ Co-Authored-By: Warp <agent@warp.dev>
## v1.4.333 (2025-11-25)
### PR [#1833](https://github.com/danielmiessler/Fabric/pull/1833) by [junaid18183](https://github.com/junaid18183): Added concall_summary
- Added concall_summery pattern to extract strategic insights from earnings transcripts for investors.
### PR [#1844](https://github.com/danielmiessler/Fabric/pull/1844) by [ksylvan](https://github.com/ksylvan): Correct directory name from `concall_summery` to `concall_summary`
- Fix: correct directory name from `concall_summery` to `concall_summary`
@@ -371,6 +421,10 @@ Co-Authored-By: Warp <agent@warp.dev>
- Add concall_summary to BUSINESS and SUMMARIZE category listings
- Add user documentation for earnings call analysis
### PR [#1833](https://github.com/danielmiessler/Fabric/pull/1833) by [junaid18183](https://github.com/junaid18183): Added concall_summery
- Added concall_summery
## v1.4.332 (2025-11-24)
### PR [#1843](https://github.com/danielmiessler/Fabric/pull/1843) by [ksylvan](https://github.com/ksylvan): Implement case-insensitive vendor and model name matching
@@ -381,11 +435,11 @@ Co-Authored-By: Warp <agent@warp.dev>
- Add FilterByVendor method with case-insensitive matching
- Add FindModelNameCaseInsensitive helper for model queries
## v1.4.331 (2025-11-22)
## v1.4.331 (2025-11-23)
### PR [#1839](https://github.com/danielmiessler/Fabric/pull/1839) by [ksylvan](https://github.com/ksylvan): Add GitHub Models Provider and Refactor Fetching Fallback Logic
- Add GitHub Models provider and refactor model fetching with direct API fallback
- Feat: add GitHub Models provider and refactor model fetching with direct API fallback
- Add GitHub Models to supported OpenAI-compatible providers list
- Implement direct HTTP fallback for non-standard model responses
- Centralize model fetching logic in openai package
@@ -395,38 +449,35 @@ Co-Authored-By: Warp <agent@warp.dev>
### PR [#1840](https://github.com/danielmiessler/Fabric/pull/1840) by [ZackaryWelch](https://github.com/ZackaryWelch): Replace deprecated bash function in completion script
- Replace deprecated bash function in completion script to use `_comp_get_words` instead of `__get_comp_words_by_ref`, fixing compatibility issues with latest bash versions and preventing script breakage on updated distributions like Fedora 42+
- Replace deprecated bash function in completion script to use `_comp_get_words` instead of the removed `__get_comp_words_by_ref` function
- Fix compatibility issues with latest bash version 5.2 and newer distributions like Fedora 42+
## v1.4.329 (2025-11-20)
### PR [#1838](https://github.com/danielmiessler/fabric/pull/1838) by [ksylvan](https://github.com/ksylvan): refactor: implement i18n support for YouTube tool error messages
### PR [#1838](https://github.com/danielmiessler/Fabric/pull/1838) by [ksylvan](https://github.com/ksylvan): refactor: implement i18n support for YouTube tool error messages
- Refactor: implement i18n support for YouTube tool error messages
- Replace hardcoded error strings with i18n translation calls
- Add localization keys for YouTube errors to all locale files
- Introduce `extractAndValidateVideoId` helper to reduce code duplication
- Update timestamp parsing logic to handle localized error formats
- Standardize error handling in `yt-dlp` execution with i18n
## v1.4.328 (2025-11-18)
### PR [#1836](https://github.com/danielmiessler/Fabric/pull/1836) by [ksylvan](https://github.com/ksylvan): docs: clarify `--raw` flag behavior for OpenAI and Anthropic providers
- Update `--raw` flag description across all documentation files
- Clarify flag only affects OpenAI-compatible providers behavior
- Document Anthropic models use smart parameter selection
- Remove outdated reference to system/user role changes
- Update help text in CLI flags definition
- Updated documentation to clarify `--raw` flag behavior across OpenAI and Anthropic providers
- Documented that Anthropic models use smart parameter selection instead of raw flag behavior
- Updated CLI help text and shell completion descriptions for better clarity
- Translated updated flag descriptions to all supported locales
- Removed outdated references to system/user role changes
### Direct commits
- Added concall_summery
## v1.4.327 (2025-11-16)
### PR [#1831](https://github.com/danielmiessler/Fabric/pull/1831) by [ksylvan](https://github.com/ksylvan): Remove `get_youtube_rss` pattern
- Chore: remove `get_youtube_rss` pattern from multiple files
- Remove `get_youtube_rss` from `pattern_explanations.md`
- Delete `get_youtube_rss` entry in `pattern_descriptions.json`
- Delete `get_youtube_rss` entry in `pattern_extracts.json`
- Remove `get_youtube_rss` from `suggest_pattern/system.md`
### PR [#1832](https://github.com/danielmiessler/Fabric/pull/1832) by [ksylvan](https://github.com/ksylvan): Improve channel management in Gemini provider
- Fix: improve channel management in Gemini streaming method
@@ -435,29 +486,29 @@ Co-Authored-By: Warp <agent@warp.dev>
- Remove redundant channel close statements from loop
- Ensure channel closes on all exit paths consistently
### PR [#1831](https://github.com/danielmiessler/Fabric/pull/1831) by [ksylvan](https://github.com/ksylvan): Remove `get_youtube_rss` pattern
- Chore: remove `get_youtube_rss` pattern from multiple files
- Remove `get_youtube_rss` from `pattern_explanations.md`
- Delete `get_youtube_rss` entry in `pattern_descriptions.json`
- Delete `get_youtube_rss` entry in `pattern_extracts.json`
- Remove `get_youtube_rss` from `suggest_pattern/system.md`
## v1.4.326 (2025-11-16)
### PR [#1830](https://github.com/danielmiessler/Fabric/pull/1830) by [ksylvan](https://github.com/ksylvan): Ensure final newline in model generated outputs
- Feat: ensure newline in `CreateOutputFile` and improve tests
- Add newline to `CreateOutputFile` if missing
- Use `t.Cleanup` for file removal in tests
- Add test for message with trailing newline
- Introduce `printedStream` flag in `Chatter.Send`
- Add newline to `CreateOutputFile` if missing and improve tests with `t.Cleanup` for file removal
- Add test for message with trailing newline and introduce `printedStream` flag in `Chatter.Send`
- Print newline if stream printed without trailing newline
### Direct commits
- Chore: update README with recent features and extensions
- Add v1.4.322 release with concept maps
- Introduce WELLNESS category with psychological analysis
- Upgrade to Claude Sonnet 4.5
- Add Portuguese language variants with BCP 47 support
- Migrate to `openai-go/azure` SDK for Azure
- Add Extensions section to README navigation
- Add v1.4.322 release with concept maps and introduce WELLNESS category with psychological analysis
- Upgrade to Claude Sonnet 4.5 and add Portuguese language variants with BCP 47 support
- Migrate to `openai-go/azure` SDK for Azure integration
- Update README with recent features and extensions, including new Extensions section navigation
- General repository maintenance and feature documentation updates
## v1.4.325 (2025-11-15)
@@ -467,21 +518,27 @@ Co-Authored-By: Warp <agent@warp.dev>
- Remove default space in `BuildSession` message content
- Trim whitespace in `anthropic` message content check
- Trim whitespace in `gemini` message content check
- Chore: incoming 1828 changelog entry
## v1.4.324 (2025-11-14)
### PR [#1827](https://github.com/danielmiessler/Fabric/pull/1827) by [ksylvan](https://github.com/ksylvan): Make YouTube API key optional in setup
- Make YouTube API key optional in setup process
- Change API key setup question to optional configuration
- Add test for optional API key behavior
- Ensure plugin configuration works without API key
- Made YouTube API key optional during setup process
- Changed API key setup question to be optional rather than required
- Added test coverage for optional API key behavior
- Ensured plugin configuration works without API key
- Added changelog entry for the changes
## v1.4.323 (2025-11-12)
### PR [#1802](https://github.com/danielmiessler/Fabric/pull/1802) by [nickarino](https://github.com/nickarino): fix: improve template extension handling for {{input}} and add examples
- Fix: improve template extension handling for {{input}} and add examples
- Extract InputSentinel constant to shared constants.go file and remove duplicate inputSentinel definitions from template.go and patterns.go
- Create withTestExtension helper function to reduce test code duplication and refactor 3 test functions to use the helper
- Fix shell script to use $@ instead of $- for proper argument quoting
- Add prominent warning at top of Extensions guide with visual indicators and update main README with brief Extensions section
### PR [#1823](https://github.com/danielmiessler/Fabric/pull/1823) by [ksylvan](https://github.com/ksylvan): Add missing patterns and renumber pattern explanations list
@@ -489,14 +546,17 @@ Co-Authored-By: Warp <agent@warp.dev>
- Add `extract_mcp_servers` pattern for MCP server identification
- Add `generate_code_rules` pattern for AI coding guardrails
- Add `t_check_dunning_kruger` pattern for competence assessment
- Renumber all patterns from 37-226 to 37-230
### Direct commits
- Chore: incoming 1823 changelog entry
- Renumber all patterns from 37-226 to 37-230 and insert new patterns at positions 37, 129, 153, 203
## v1.4.322 (2025-11-05)
### PR [#1816](https://github.com/danielmiessler/Fabric/pull/1816) by [ksylvan](https://github.com/ksylvan): Update `anthropic-sdk-go` to v1.16.0 and update models
- Upgrade `anthropic-sdk-go` to version 1.16.0
- Remove outdated model `ModelClaude3_5SonnetLatest`
- Add new model `ModelClaudeSonnet4_5_20250929`
- Include `ModelClaudeSonnet4_5_20250929` in `modelBetas` map
### PR [#1814](https://github.com/danielmiessler/Fabric/pull/1814) by [ksylvan](https://github.com/ksylvan): Add Concept Map in html
- Add `create_conceptmap` for interactive HTML concept maps using Vis.js
@@ -504,71 +564,60 @@ Co-Authored-By: Warp <agent@warp.dev>
- Introduce `model_as_sherlock_freud` for psychological modeling and behavior analysis
- Implement `predict_person_actions` for behavioral response predictions
- Add `recommend_yoga_practice` for personalized yoga guidance
- Credit goes to @FELIPEGUEDESBR for the pattern
### PR [#1816](https://github.com/danielmiessler/Fabric/pull/1816) by [ksylvan](https://github.com/ksylvan): Update `anthropic-sdk-go` to v1.16.0 and update models
- Upgraded `anthropic-sdk-go` from v1.13.0 to v1.16.0
- Removed outdated model `ModelClaude3_5SonnetLatest`
- Added new model `ModelClaudeSonnet4_5_20250929`
- Updated anthropic beta map to include the new model
- Updated dependencies in `go.sum` file
## v1.4.321 (2025-11-03)
### PR [#1803](https://github.com/danielmiessler/Fabric/pull/1803) by [dependabot[bot][bot]](https://github.com/apps/dependabot): chore(deps-dev): bump vite from 5.4.20 to 5.4.21 in /web in the npm_and_yarn group across 1 directory
### PR [#1803](https://github.com/danielmiessler/Fabric/pull/1803) by [dependabot[bot]](https://github.com/apps/dependabot): chore(deps-dev): bump vite from 5.4.20 to 5.4.21 in /web in the npm_and_yarn group across 1 directory
- Updated Vite development dependency from version 5.4.20 to 5.4.21 in the web directory
- Bumped vite dependency from 5.4.20 to 5.4.21 in the /web directory
### PR [#1805](https://github.com/danielmiessler/Fabric/pull/1805) by [OmriH-Elister](https://github.com/OmriH-Elister): Added several new patterns
- Added new WELLNESS category with four patterns including personalized yoga practice recommendations and wellness guidance
- Added `model_as_sherlock_freud` pattern for psychological detective analysis combining Sherlock Holmes deduction with Freudian psychology
- Added `predict_person_actions` pattern for behavioral response predictions based on personality analysis
- Added `fix_typos` pattern for automated proofreading and typo corrections
- Updated ANALYSIS and SELF categories to include new wellness-related patterns and classifications
- Added new WELLNESS category with four patterns including yoga practice recommendations
- Introduced psychological analysis patterns: `model_as_sherlock_freud` and `predict_person_actions`
- Added `fix_typos` pattern for proofreading and text corrections
- Updated ANALYSIS and SELF categories to include new wellness-related patterns
### PR [#1808](https://github.com/danielmiessler/Fabric/pull/1808) by [sluosapher](https://github.com/sluosapher): Updated create_newsletter_entry pattern to generate more factual titles
- Updated the title generation style; added an output example.
- Updated title generation style for more factual newsletter entries and added output example
## v1.4.320 (2025-10-28)
### PR [#1780](https://github.com/danielmiessler/Fabric/pull/1780) by [marcas756](https://github.com/marcas756): feat: add extract_characters pattern
- Define character extraction goals and steps with canonical naming and deduplication rules
- Outline interaction mapping and narrative importance analysis
- Provide comprehensive output schema with proper formatting guidelines
- Include positive and negative examples for pattern clarity
- Enforce restrictions on speculative motivations and non-actor inclusion
### PR [#1794](https://github.com/danielmiessler/Fabric/pull/1794) by [starfish456](https://github.com/starfish456): Enhance web app docs
- Remove duplicate content from the main readme and link to the web app readme
- Update table of contents with proper nesting and fix minor formatting issues
### PR [#1810](https://github.com/danielmiessler/Fabric/pull/1810) by [tonymet](https://github.com/tonymet): improve subtitle lang, retry, debugging & error handling
- Improve subtitle lang, retry, debugging & error handling
### PR [#1780](https://github.com/danielmiessler/Fabric/pull/1780) by [marcas756](https://github.com/marcas756): feat: add extract_characters pattern
- Add extract_characters pattern for detailed character analysis and identification
- Define character extraction goals with canonical naming and deduplication rules
- Include output schema with formatting guidelines and positive/negative examples
### PR [#1794](https://github.com/danielmiessler/Fabric/pull/1794) by [productStripesAdmin](https://github.com/productStripesAdmin): Enhance web app docs
- Remove duplicate content from main readme and link to web app readme
- Update table of contents with proper nesting and fix minor formatting issues
### Direct commits
- Docs: clean up README - remove duplicate image and add collapsible updates section
- Remove duplicate fabric-summarize.png screenshot
- Wrap Updates section in HTML details/summary accordion to save space
🤖 Generated with [Claude Code](<https://claude.com/claude-code)>
Co-Authored-By: Claude <noreply@anthropic.com>
- Updated CSE pattern.
- Add new patterns and update title generation style with output examples
- Fix template extension handling for {{input}} and add examples
## v1.4.319 (2025-09-30)
### PR [#1783](https://github.com/danielmiessler/Fabric/pull/1783) by [ksylvan](https://github.com/ksylvan): Update anthropic-sdk-go and add claude-sonnet-4-5
- Feat: update `anthropic-sdk-go` to v1.13.0 and add new model
- Upgrade `anthropic-sdk-go` to version 1.13.0
- Add `ModelClaudeSonnet4_5` to supported models list
- Updated `anthropic-sdk-go` to version 1.13.0 for improved compatibility and performance
- Added support for `ModelClaudeSonnet4_5` to the list of available AI models
### Direct commits
- Added new `extract_characters` system definition with comprehensive character extraction capabilities
- Implemented canonical naming and deduplication rules for consistent character identification
- Created structured output schema with detailed formatting guidelines and examples
- Established interaction mapping functionality to track character relationships and narrative importance
- Added fallback handling for scenarios where no characters are found in the content
## v1.4.318 (2025-09-24)
@@ -594,28 +643,19 @@ Co-Authored-By: Claude <noreply@anthropic.com>
### PR [#1777](https://github.com/danielmiessler/Fabric/pull/1777) by [ksylvan](https://github.com/ksylvan): chore: remove garble installation from release workflow
- Remove garble installation step from release workflow
- Add comment for GoReleaser config file reference link
- The original idea of adding garble was to make it pass
virus scanning during version upgrades for Winget, and
this was a failed experiment.
- Remove garble installation step from release workflow to simplify the build process
- Add comment with GoReleaser config file reference link for better documentation
- Discontinue failed experiment with garble that was intended to improve Windows package manager virus scanning compatibility
## v1.4.315 (2025-09-20)
### Direct commits
### PR [#1776](https://github.com/danielmiessler/Fabric/pull/1776) by [ksylvan](https://github.com/ksylvan): Remove garble from the build process for Windows
- Chore: update CI workflow and simplify goreleaser build configuration
- Add changelog database to git tracking
- Remove unnecessary goreleaser comments
- Add version metadata to default build
- Rename windows build from garbled to standard
- Remove garble obfuscation from windows build
- Standardize ldflags across all build targets
- Inject version info during compilation
- Update CI workflow and simplify goreleaser build configuration
- Add changelog database to git tracking
## v1.4.314 (2025-09-17)

View File

@@ -705,6 +705,7 @@ Application Options:
--yt-dlp-args= Additional arguments to pass to yt-dlp (e.g. '--cookies-from-browser brave')
--thinking= Set reasoning/thinking level (e.g., off, low, medium, high, or
numeric tokens for Anthropic or Google Gemini)
--show-metadata Print metadata (input/output tokens) to stderr
--debug= Set debug level (0: off, 1: basic, 2: detailed, 3: trace)
Help Options:
-h, --help Show this help message

View File

@@ -131,6 +131,75 @@ func ScanDirectory(rootDir string, maxDepth int, instructions string, ignoreList
return json.MarshalIndent(data, "", " ")
}
// ScanFiles scans specific files and returns a JSON representation
func ScanFiles(files []string, instructions string) ([]byte, error) {
fileCount := 0
dirSet := make(map[string]bool)
// Create root directory item
rootItem := FileItem{
Type: "directory",
Name: ".",
Contents: []FileItem{},
}
for _, filePath := range files {
// Skip directories
info, err := os.Stat(filePath)
if err != nil {
return nil, fmt.Errorf("error accessing file %s: %v", filePath, err)
}
if info.IsDir() {
continue
}
// Track unique directories
dir := filepath.Dir(filePath)
if dir != "." {
dirSet[dir] = true
}
fileCount++
// Read file content
content, err := os.ReadFile(filePath)
if err != nil {
return nil, fmt.Errorf("error reading file %s: %v", filePath, err)
}
// Clean path for consistent handling
cleanPath := filepath.Clean(filePath)
if strings.HasPrefix(cleanPath, "./") {
cleanPath = cleanPath[2:]
}
// Add file to the structure
addFileToDirectory(&rootItem, cleanPath, string(content), ".")
}
// Create final data structure
var data []any
data = append(data, rootItem)
// Add report
reportItem := map[string]any{
"type": "report",
"directories": len(dirSet) + 1,
"files": fileCount,
}
data = append(data, reportItem)
// Add instructions
instructionsItem := map[string]any{
"type": "instructions",
"name": "code_change_instructions",
"details": instructions,
}
data = append(data, instructionsItem)
return json.MarshalIndent(data, "", " ")
}
// addFileToDirectory adds a file to the correct directory in the structure
func addFileToDirectory(root *FileItem, path, content, rootDir string) {
parts := strings.Split(path, string(filepath.Separator))

View File

@@ -0,0 +1,100 @@
package main
import (
"encoding/json"
"os"
"path/filepath"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestScanFiles(t *testing.T) {
// Create temp directory with test files
tmpDir := t.TempDir()
// Create test files
file1 := filepath.Join(tmpDir, "test1.go")
file2 := filepath.Join(tmpDir, "test2.go")
subDir := filepath.Join(tmpDir, "subdir")
file3 := filepath.Join(subDir, "test3.go")
require.NoError(t, os.WriteFile(file1, []byte("package main\n"), 0644))
require.NoError(t, os.WriteFile(file2, []byte("package main\n\nfunc main() {}\n"), 0644))
require.NoError(t, os.MkdirAll(subDir, 0755))
require.NoError(t, os.WriteFile(file3, []byte("package subdir\n"), 0644))
// Test scanning specific files
files := []string{file1, file3}
instructions := "Test instructions"
jsonData, err := ScanFiles(files, instructions)
require.NoError(t, err)
// Parse the JSON output
var result []any
err = json.Unmarshal(jsonData, &result)
require.NoError(t, err)
assert.Len(t, result, 3) // directory, report, instructions
// Check report
report := result[1].(map[string]any)
assert.Equal(t, "report", report["type"])
assert.Equal(t, float64(2), report["files"])
// Check instructions
instr := result[2].(map[string]any)
assert.Equal(t, "instructions", instr["type"])
assert.Equal(t, "Test instructions", instr["details"])
}
func TestScanFilesSkipsDirectories(t *testing.T) {
tmpDir := t.TempDir()
file1 := filepath.Join(tmpDir, "test.go")
subDir := filepath.Join(tmpDir, "subdir")
require.NoError(t, os.WriteFile(file1, []byte("package main\n"), 0644))
require.NoError(t, os.MkdirAll(subDir, 0755))
// Include a directory in the file list - should be skipped
files := []string{file1, subDir}
jsonData, err := ScanFiles(files, "test")
require.NoError(t, err)
var result []any
err = json.Unmarshal(jsonData, &result)
require.NoError(t, err)
// Check that only 1 file was counted (directory was skipped)
report := result[1].(map[string]any)
assert.Equal(t, float64(1), report["files"])
}
func TestScanFilesNonExistentFile(t *testing.T) {
files := []string{"/nonexistent/file.go"}
_, err := ScanFiles(files, "test")
assert.Error(t, err)
assert.Contains(t, err.Error(), "error accessing file")
}
func TestScanDirectory(t *testing.T) {
tmpDir := t.TempDir()
file1 := filepath.Join(tmpDir, "main.go")
require.NoError(t, os.WriteFile(file1, []byte("package main\n"), 0644))
jsonData, err := ScanDirectory(tmpDir, 3, "Test instructions", []string{})
require.NoError(t, err)
var result []any
err = json.Unmarshal(jsonData, &result)
require.NoError(t, err)
assert.Len(t, result, 3)
// Check instructions
instr := result[2].(map[string]any)
assert.Equal(t, "Test instructions", instr["details"])
}

View File

@@ -1,6 +1,7 @@
package main
import (
"bufio"
"flag"
"fmt"
"os"
@@ -15,25 +16,65 @@ func main() {
flag.Usage = printUsage
flag.Parse()
// Require exactly two positional arguments: directory and instructions
if flag.NArg() != 2 {
printUsage()
os.Exit(1)
// Check if stdin has data (is a pipe)
stdinInfo, _ := os.Stdin.Stat()
hasStdin := (stdinInfo.Mode() & os.ModeCharDevice) == 0
var jsonData []byte
var err error
if hasStdin {
// Stdin mode: read file list from stdin, instructions from argument
if flag.NArg() != 1 {
fmt.Fprintf(os.Stderr, "Error: When piping file list via stdin, provide exactly 1 argument: <instructions>\n")
fmt.Fprintf(os.Stderr, "Usage: find . -name '*.go' | code_helper \"instructions\"\n")
os.Exit(1)
}
instructions := flag.Arg(0)
// Read file paths from stdin
var files []string
scanner := bufio.NewScanner(os.Stdin)
for scanner.Scan() {
line := strings.TrimSpace(scanner.Text())
if line != "" {
files = append(files, line)
}
}
if err := scanner.Err(); err != nil {
fmt.Fprintf(os.Stderr, "Error reading stdin: %v\n", err)
os.Exit(1)
}
if len(files) == 0 {
fmt.Fprintf(os.Stderr, "Error: No files provided via stdin\n")
os.Exit(1)
}
jsonData, err = ScanFiles(files, instructions)
} else {
// Directory mode: require directory and instructions arguments
if flag.NArg() != 2 {
printUsage()
os.Exit(1)
}
directory := flag.Arg(0)
instructions := flag.Arg(1)
// Validate directory
if info, err := os.Stat(directory); err != nil || !info.IsDir() {
fmt.Fprintf(os.Stderr, "Error: Directory '%s' does not exist or is not a directory\n", directory)
os.Exit(1)
}
// Parse ignore patterns and scan directory
jsonData, err = ScanDirectory(directory, *maxDepth, instructions, strings.Split(*ignorePatterns, ","))
}
directory := flag.Arg(0)
instructions := flag.Arg(1)
// Validate directory
if info, err := os.Stat(directory); err != nil || !info.IsDir() {
fmt.Fprintf(os.Stderr, "Error: Directory '%s' does not exist or is not a directory\n", directory)
os.Exit(1)
}
// Parse ignore patterns and scan directory
jsonData, err := ScanDirectory(directory, *maxDepth, instructions, strings.Split(*ignorePatterns, ","))
if err != nil {
fmt.Fprintf(os.Stderr, "Error scanning directory: %v\n", err)
fmt.Fprintf(os.Stderr, "Error scanning: %v\n", err)
os.Exit(1)
}
@@ -53,11 +94,14 @@ func printUsage() {
Usage:
code_helper [options] <directory> <instructions>
<file_list> | code_helper [options] <instructions>
Examples:
code_helper . "Add input validation to all user inputs"
code_helper -depth 4 ./my-project "Implement error handling"
code_helper -out project.json ./src "Fix security issues"
find . -name '*.go' | code_helper "Refactor error handling"
git ls-files '*.py' | code_helper "Add type hints"
Options:
`)

View File

@@ -1,3 +1,3 @@
package main
var version = "v1.4.366"
var version = "v1.4.373"

Binary file not shown.

View File

@@ -202,14 +202,23 @@ func (c *Cache) GetVersions() (map[string]*git.Version, error) {
}
if dateStr.Valid {
// Try RFC3339Nano first (for nanosecond precision), then fall back to RFC3339
v.Date, err = time.Parse(time.RFC3339Nano, dateStr.String)
if err != nil {
v.Date, err = time.Parse(time.RFC3339, dateStr.String)
if err != nil {
fmt.Fprintf(os.Stderr, "Error parsing date '%s' for version '%s': %v. Expected format: RFC3339 or RFC3339Nano.\n", dateStr.String, v.Name, err)
// Try multiple date formats: SQLite format, RFC3339Nano, and RFC3339
dateFormats := []string{
"2006-01-02 15:04:05-07:00", // SQLite DATETIME format
"2006-01-02 15:04:05.999999999-07:00", // SQLite with fractional seconds
time.RFC3339Nano,
time.RFC3339,
}
var parseErr error
for _, format := range dateFormats {
v.Date, parseErr = time.Parse(format, dateStr.String)
if parseErr == nil {
break // Successfully parsed
}
}
if parseErr != nil {
fmt.Fprintf(os.Stderr, "Error parsing date '%s' for version '%s': %v\n", dateStr.String, v.Name, parseErr)
}
}
if prNumbersJSON != "" {

View File

@@ -470,7 +470,8 @@ func (g *Generator) generateRawVersionContent(version *git.Version) string {
}
// There are occasionally no PRs or direct commits other than version bumps, so we handle that gracefully
if len(prCommits) == 0 && len(directCommits) == 0 {
// However, don't return early if we have PRs to output from version.PRNumbers
if len(prCommits) == 0 && len(directCommits) == 0 && len(version.PRNumbers) == 0 {
return ""
}

View File

@@ -284,6 +284,20 @@ func (g *Generator) CreateNewChangelogEntry(version string) error {
}
}
// Update metadata before staging changes so they get committed together
if g.cache != nil {
// Update last_processed_tag to the version we just processed
if err := g.cache.SetLastProcessedTag(version); err != nil {
fmt.Fprintf(os.Stderr, "Warning: Failed to update last_processed_tag: %v\n", err)
}
// Update last_pr_sync to the version date (not current time)
// This ensures future runs will fetch PRs merged after this version
if err := g.cache.SetLastPRSync(versionDate); err != nil {
fmt.Fprintf(os.Stderr, "Warning: Failed to update last_pr_sync: %v\n", err)
}
}
if err := g.stageChangesForRelease(); err != nil {
return fmt.Errorf("critical: failed to stage changes for release: %w", err)
}

View File

@@ -24,30 +24,4 @@ Take a step back and think step-by-step about how to achieve the best possible r
# INPUT
INPUT:# IDENTITY and PURPOSE
You are an AI assistant whose primary responsibility is to interpret and analyze psychological profiles and/or psychology data files provided as input. Your role is to carefully process this data and use your expertise to develop a tailored plan aimed at spiritual and mental healing, as well as overall life improvement for the subject. You must approach each case with sensitivity, applying psychological knowledge and holistic strategies to create actionable, personalized recommendations that address both mental and spiritual well-being. Your focus is on structured, compassionate, and practical guidance that can help the individual make meaningful improvements in their life.
Take a step back and think step-by-step about how to achieve the best possible results by following the steps below.
# STEPS
- Carefully review the psychological-profile and/or psychology data file provided as input.
- Analyze the data to identify key issues, strengths, and areas needing improvement related to the subject's mental and spiritual well-being.
- Develop a comprehensive plan that includes specific strategies for spiritual healing, mental health improvement, and overall life enhancement.
- Structure your output to clearly outline recommendations, resources, and actionable steps tailored to the individual's unique profile.
# OUTPUT INSTRUCTIONS
- Only output Markdown.
- Ensure your output is organized, clear, and easy to follow, using headings, subheadings, and bullet points where appropriate.
- Ensure you follow ALL these instructions when creating your output.
# INPUT
INPUT:
INPUT

View File

@@ -289,6 +289,20 @@ const docTemplate = `{
"ThinkingHigh"
]
},
"domain.UsageMetadata": {
"type": "object",
"properties": {
"input_tokens": {
"type": "integer"
},
"output_tokens": {
"type": "integer"
},
"total_tokens": {
"type": "integer"
}
}
},
"fsdb.Pattern": {
"type": "object",
"properties": {
@@ -360,6 +374,9 @@ const docTemplate = `{
"$ref": "#/definitions/restapi.PromptRequest"
}
},
"quiet": {
"type": "boolean"
},
"raw": {
"type": "boolean"
},
@@ -372,6 +389,9 @@ const docTemplate = `{
"seed": {
"type": "integer"
},
"showMetadata": {
"type": "boolean"
},
"suppressThink": {
"type": "boolean"
},
@@ -392,6 +412,9 @@ const docTemplate = `{
"type": "number",
"format": "float64"
},
"updateChan": {
"type": "object"
},
"voice": {
"type": "string"
}
@@ -423,6 +446,10 @@ const docTemplate = `{
"patternName": {
"type": "string"
},
"sessionName": {
"description": "Session name for multi-turn conversations",
"type": "string"
},
"strategyName": {
"description": "Optional strategy name",
"type": "string"
@@ -446,7 +473,6 @@ const docTemplate = `{
"type": "object",
"properties": {
"content": {
"description": "The actual content",
"type": "string"
},
"format": {
@@ -454,8 +480,11 @@ const docTemplate = `{
"type": "string"
},
"type": {
"description": "\"content\", \"error\", \"complete\"",
"description": "\"content\", \"usage\", \"error\", \"complete\"",
"type": "string"
},
"usage": {
"$ref": "#/definitions/domain.UsageMetadata"
}
}
},

View File

@@ -283,6 +283,20 @@
"ThinkingHigh"
]
},
"domain.UsageMetadata": {
"type": "object",
"properties": {
"input_tokens": {
"type": "integer"
},
"output_tokens": {
"type": "integer"
},
"total_tokens": {
"type": "integer"
}
}
},
"fsdb.Pattern": {
"type": "object",
"properties": {
@@ -354,6 +368,9 @@
"$ref": "#/definitions/restapi.PromptRequest"
}
},
"quiet": {
"type": "boolean"
},
"raw": {
"type": "boolean"
},
@@ -366,6 +383,9 @@
"seed": {
"type": "integer"
},
"showMetadata": {
"type": "boolean"
},
"suppressThink": {
"type": "boolean"
},
@@ -386,6 +406,9 @@
"type": "number",
"format": "float64"
},
"updateChan": {
"type": "object"
},
"voice": {
"type": "string"
}
@@ -417,6 +440,10 @@
"patternName": {
"type": "string"
},
"sessionName": {
"description": "Session name for multi-turn conversations",
"type": "string"
},
"strategyName": {
"description": "Optional strategy name",
"type": "string"
@@ -440,7 +467,6 @@
"type": "object",
"properties": {
"content": {
"description": "The actual content",
"type": "string"
},
"format": {
@@ -448,8 +474,11 @@
"type": "string"
},
"type": {
"description": "\"content\", \"error\", \"complete\"",
"description": "\"content\", \"usage\", \"error\", \"complete\"",
"type": "string"
},
"usage": {
"$ref": "#/definitions/domain.UsageMetadata"
}
}
},

View File

@@ -12,6 +12,15 @@ definitions:
- ThinkingLow
- ThinkingMedium
- ThinkingHigh
domain.UsageMetadata:
properties:
input_tokens:
type: integer
output_tokens:
type: integer
total_tokens:
type: integer
type: object
fsdb.Pattern:
properties:
description:
@@ -60,6 +69,8 @@ definitions:
items:
$ref: '#/definitions/restapi.PromptRequest'
type: array
quiet:
type: boolean
raw:
type: boolean
search:
@@ -68,6 +79,8 @@ definitions:
type: string
seed:
type: integer
showMetadata:
type: boolean
suppressThink:
type: boolean
temperature:
@@ -82,6 +95,8 @@ definitions:
topP:
format: float64
type: number
updateChan:
type: object
voice:
type: string
type: object
@@ -102,6 +117,9 @@ definitions:
type: string
patternName:
type: string
sessionName:
description: Session name for multi-turn conversations
type: string
strategyName:
description: Optional strategy name
type: string
@@ -118,14 +136,15 @@ definitions:
restapi.StreamResponse:
properties:
content:
description: The actual content
type: string
format:
description: '"markdown", "mermaid", "plain"'
type: string
type:
description: '"content", "error", "complete"'
description: '"content", "usage", "error", "complete"'
type: string
usage:
$ref: '#/definitions/domain.UsageMetadata'
type: object
restapi.YouTubeRequest:
properties:

View File

@@ -104,6 +104,7 @@ type Flags struct {
Notification bool `long:"notification" yaml:"notification" description:"Send desktop notification when command completes"`
NotificationCommand string `long:"notification-command" yaml:"notificationCommand" description:"Custom command to run for notifications (overrides built-in notifications)"`
Thinking domain.ThinkingLevel `long:"thinking" yaml:"thinking" description:"Set reasoning/thinking level (e.g., off, low, medium, high, or numeric tokens for Anthropic or Google Gemini)"`
ShowMetadata bool `long:"show-metadata" description:"Print metadata to stderr"`
Debug int `long:"debug" description:"Set debug level (0=off, 1=basic, 2=detailed, 3=trace)" default:"0"`
}
@@ -459,6 +460,7 @@ func (o *Flags) BuildChatOptions() (ret *domain.ChatOptions, err error) {
Voice: o.Voice,
Notification: o.Notification || o.NotificationCommand != "",
NotificationCommand: o.NotificationCommand,
ShowMetadata: o.ShowMetadata,
}
return
}

View File

@@ -64,7 +64,7 @@ func (o *Chatter) Send(request *domain.ChatRequest, opts *domain.ChatOptions) (s
message := ""
if o.Stream {
responseChan := make(chan string)
responseChan := make(chan domain.StreamUpdate)
errChan := make(chan error, 1)
done := make(chan struct{})
printedStream := false
@@ -76,15 +76,31 @@ func (o *Chatter) Send(request *domain.ChatRequest, opts *domain.ChatOptions) (s
}
}()
for response := range responseChan {
message += response
if !opts.SuppressThink {
fmt.Print(response)
printedStream = true
for update := range responseChan {
if opts.UpdateChan != nil {
opts.UpdateChan <- update
}
switch update.Type {
case domain.StreamTypeContent:
message += update.Content
if !opts.SuppressThink && !opts.Quiet {
fmt.Print(update.Content)
printedStream = true
}
case domain.StreamTypeUsage:
if opts.ShowMetadata && update.Usage != nil && !opts.Quiet {
fmt.Fprintf(os.Stderr, "\n[Metadata] Input: %d | Output: %d | Total: %d\n",
update.Usage.InputTokens, update.Usage.OutputTokens, update.Usage.TotalTokens)
}
case domain.StreamTypeError:
if !opts.Quiet {
fmt.Fprintf(os.Stderr, "Error: %s\n", update.Content)
}
errChan <- errors.New(update.Content)
}
}
if printedStream && !opts.SuppressThink && !strings.HasSuffix(message, "\n") {
if printedStream && !opts.SuppressThink && !strings.HasSuffix(message, "\n") && !opts.Quiet {
fmt.Println()
}

View File

@@ -14,7 +14,7 @@ import (
// mockVendor implements the ai.Vendor interface for testing
type mockVendor struct {
sendStreamError error
streamChunks []string
streamChunks []domain.StreamUpdate
sendFunc func(context.Context, []*chat.ChatCompletionMessage, *domain.ChatOptions) (string, error)
}
@@ -45,7 +45,7 @@ func (m *mockVendor) ListModels() ([]string, error) {
return []string{"test-model"}, nil
}
func (m *mockVendor) SendStream(messages []*chat.ChatCompletionMessage, opts *domain.ChatOptions, responseChan chan string) error {
func (m *mockVendor) SendStream(messages []*chat.ChatCompletionMessage, opts *domain.ChatOptions, responseChan chan domain.StreamUpdate) error {
// Send chunks if provided (for successful streaming test)
if m.streamChunks != nil {
for _, chunk := range m.streamChunks {
@@ -169,7 +169,11 @@ func TestChatter_Send_StreamingSuccessfulAggregation(t *testing.T) {
db := fsdb.NewDb(tempDir)
// Create test chunks that should be aggregated
testChunks := []string{"Hello", " ", "world", "!", " This", " is", " a", " test."}
chunks := []string{"Hello", " ", "world", "!", " This", " is", " a", " test."}
testChunks := make([]domain.StreamUpdate, len(chunks))
for i, c := range chunks {
testChunks[i] = domain.StreamUpdate{Type: domain.StreamTypeContent, Content: c}
}
expectedMessage := "Hello world! This is a test."
// Create a mock vendor that will send chunks successfully
@@ -228,3 +232,83 @@ func TestChatter_Send_StreamingSuccessfulAggregation(t *testing.T) {
t.Errorf("Expected aggregated message %q, got %q", expectedMessage, assistantMessage.Content)
}
}
func TestChatter_Send_StreamingMetadataPropagation(t *testing.T) {
// Create a temporary database for testing
tempDir := t.TempDir()
db := fsdb.NewDb(tempDir)
// Create test chunks: one content, one usage metadata
testChunks := []domain.StreamUpdate{
{
Type: domain.StreamTypeContent,
Content: "Test content",
},
{
Type: domain.StreamTypeUsage,
Usage: &domain.UsageMetadata{
InputTokens: 10,
OutputTokens: 5,
TotalTokens: 15,
},
},
}
// Create a mock vendor
mockVendor := &mockVendor{
sendStreamError: nil,
streamChunks: testChunks,
}
// Create chatter with streaming enabled
chatter := &Chatter{
db: db,
Stream: true,
vendor: mockVendor,
model: "test-model",
}
// Create a test request
request := &domain.ChatRequest{
Message: &chat.ChatCompletionMessage{
Role: chat.ChatMessageRoleUser,
Content: "test message",
},
}
// Create an update channel to capture stream events
updateChan := make(chan domain.StreamUpdate, 10)
// Create test options with UpdateChan
opts := &domain.ChatOptions{
Model: "test-model",
UpdateChan: updateChan,
Quiet: true, // Suppress stdout/stderr
}
// Call Send
_, err := chatter.Send(request, opts)
if err != nil {
t.Fatalf("Expected no error, but got: %v", err)
}
close(updateChan)
// Verify we received the metadata event
var usageReceived bool
for update := range updateChan {
if update.Type == domain.StreamTypeUsage {
usageReceived = true
if update.Usage == nil {
t.Error("Expected usage metadata to be non-nil")
} else {
if update.Usage.TotalTokens != 15 {
t.Errorf("Expected 15 total tokens, got %d", update.Usage.TotalTokens)
}
}
}
}
if !usageReceived {
t.Error("Expected to receive a usage metadata update, but didn't")
}
}

View File

@@ -43,7 +43,7 @@ func (m *testVendor) Configure() error { return nil }
func (m *testVendor) Setup() error { return nil }
func (m *testVendor) SetupFillEnvFileContent(*bytes.Buffer) {}
func (m *testVendor) ListModels() ([]string, error) { return m.models, nil }
func (m *testVendor) SendStream([]*chat.ChatCompletionMessage, *domain.ChatOptions, chan string) error {
func (m *testVendor) SendStream([]*chat.ChatCompletionMessage, *domain.ChatOptions, chan domain.StreamUpdate) error {
return nil
}
func (m *testVendor) Send(context.Context, []*chat.ChatCompletionMessage, *domain.ChatOptions) (string, error) {

View File

@@ -51,6 +51,9 @@ type ChatOptions struct {
Voice string
Notification bool
NotificationCommand string
ShowMetadata bool
Quiet bool
UpdateChan chan StreamUpdate
}
// NormalizeMessages remove empty messages and ensure messages order user-assist-user

24
internal/domain/stream.go Normal file
View File

@@ -0,0 +1,24 @@
package domain
// StreamType distinguishes between partial text content and metadata events.
type StreamType string
const (
StreamTypeContent StreamType = "content"
StreamTypeUsage StreamType = "usage"
StreamTypeError StreamType = "error"
)
// StreamUpdate is the unified payload sent through the internal channels.
type StreamUpdate struct {
Type StreamType `json:"type"`
Content string `json:"content,omitempty"` // For text deltas
Usage *UsageMetadata `json:"usage,omitempty"` // For token counts
}
// UsageMetadata normalizes token counts across different providers.
type UsageMetadata struct {
InputTokens int `json:"input_tokens"`
OutputTokens int `json:"output_tokens"`
TotalTokens int `json:"total_tokens"`
}

View File

@@ -184,7 +184,7 @@ func parseThinking(level domain.ThinkingLevel) (anthropic.ThinkingConfigParamUni
}
func (an *Client) SendStream(
msgs []*chat.ChatCompletionMessage, opts *domain.ChatOptions, channel chan string,
msgs []*chat.ChatCompletionMessage, opts *domain.ChatOptions, channel chan domain.StreamUpdate,
) (err error) {
messages := an.toMessages(msgs)
if len(messages) == 0 {
@@ -210,9 +210,33 @@ func (an *Client) SendStream(
for stream.Next() {
event := stream.Current()
// directly send any non-empty delta text
// Handle Content
if event.Delta.Text != "" {
channel <- event.Delta.Text
channel <- domain.StreamUpdate{
Type: domain.StreamTypeContent,
Content: event.Delta.Text,
}
}
// Handle Usage
if event.Message.Usage.InputTokens != 0 || event.Message.Usage.OutputTokens != 0 {
channel <- domain.StreamUpdate{
Type: domain.StreamTypeUsage,
Usage: &domain.UsageMetadata{
InputTokens: int(event.Message.Usage.InputTokens),
OutputTokens: int(event.Message.Usage.OutputTokens),
TotalTokens: int(event.Message.Usage.InputTokens + event.Message.Usage.OutputTokens),
},
}
} else if event.Usage.InputTokens != 0 || event.Usage.OutputTokens != 0 {
channel <- domain.StreamUpdate{
Type: domain.StreamTypeUsage,
Usage: &domain.UsageMetadata{
InputTokens: int(event.Usage.InputTokens),
OutputTokens: int(event.Usage.OutputTokens),
TotalTokens: int(event.Usage.InputTokens + event.Usage.OutputTokens),
},
}
}
}

View File

@@ -154,7 +154,7 @@ func (c *BedrockClient) ListModels() ([]string, error) {
}
// SendStream sends the messages to the Bedrock ConverseStream API
func (c *BedrockClient) SendStream(msgs []*chat.ChatCompletionMessage, opts *domain.ChatOptions, channel chan string) (err error) {
func (c *BedrockClient) SendStream(msgs []*chat.ChatCompletionMessage, opts *domain.ChatOptions, channel chan domain.StreamUpdate) (err error) {
// Ensure channel is closed on all exit paths to prevent goroutine leaks
defer func() {
if r := recover(); r != nil {
@@ -186,18 +186,35 @@ func (c *BedrockClient) SendStream(msgs []*chat.ChatCompletionMessage, opts *dom
case *types.ConverseStreamOutputMemberContentBlockDelta:
text, ok := v.Value.Delta.(*types.ContentBlockDeltaMemberText)
if ok {
channel <- text.Value
channel <- domain.StreamUpdate{
Type: domain.StreamTypeContent,
Content: text.Value,
}
}
case *types.ConverseStreamOutputMemberMessageStop:
channel <- "\n"
channel <- domain.StreamUpdate{
Type: domain.StreamTypeContent,
Content: "\n",
}
return nil // Let defer handle the close
case *types.ConverseStreamOutputMemberMetadata:
if v.Value.Usage != nil {
channel <- domain.StreamUpdate{
Type: domain.StreamTypeUsage,
Usage: &domain.UsageMetadata{
InputTokens: int(*v.Value.Usage.InputTokens),
OutputTokens: int(*v.Value.Usage.OutputTokens),
TotalTokens: int(*v.Value.Usage.TotalTokens),
},
}
}
// Unused Events
case *types.ConverseStreamOutputMemberMessageStart,
*types.ConverseStreamOutputMemberContentBlockStart,
*types.ConverseStreamOutputMemberContentBlockStop,
*types.ConverseStreamOutputMemberMetadata:
*types.ConverseStreamOutputMemberContentBlockStop:
default:
return fmt.Errorf("unknown stream event type: %T", v)

View File

@@ -108,12 +108,30 @@ func (c *Client) constructRequest(msgs []*chat.ChatCompletionMessage, opts *doma
return builder.String()
}
func (c *Client) SendStream(msgs []*chat.ChatCompletionMessage, opts *domain.ChatOptions, channel chan string) error {
func (c *Client) SendStream(msgs []*chat.ChatCompletionMessage, opts *domain.ChatOptions, channel chan domain.StreamUpdate) error {
defer close(channel)
request := c.constructRequest(msgs, opts)
channel <- request
channel <- "\n"
channel <- DryRunResponse
channel <- domain.StreamUpdate{
Type: domain.StreamTypeContent,
Content: request,
}
channel <- domain.StreamUpdate{
Type: domain.StreamTypeContent,
Content: "\n",
}
channel <- domain.StreamUpdate{
Type: domain.StreamTypeContent,
Content: DryRunResponse,
}
// Simulated usage
channel <- domain.StreamUpdate{
Type: domain.StreamTypeUsage,
Usage: &domain.UsageMetadata{
InputTokens: 100,
OutputTokens: 50,
TotalTokens: 150,
},
}
return nil
}

View File

@@ -39,7 +39,7 @@ func TestSendStream_SendsMessages(t *testing.T) {
opts := &domain.ChatOptions{
Model: "dry-run-model",
}
channel := make(chan string)
channel := make(chan domain.StreamUpdate)
go func() {
err := client.SendStream(msgs, opts, channel)
if err != nil {
@@ -48,7 +48,7 @@ func TestSendStream_SendsMessages(t *testing.T) {
}()
var receivedMessages []string
for msg := range channel {
receivedMessages = append(receivedMessages, msg)
receivedMessages = append(receivedMessages, msg.Content)
}
if len(receivedMessages) == 0 {
t.Errorf("Expected to receive messages, but got none")

View File

@@ -129,7 +129,7 @@ func (o *Client) Send(ctx context.Context, msgs []*chat.ChatCompletionMessage, o
return
}
func (o *Client) SendStream(msgs []*chat.ChatCompletionMessage, opts *domain.ChatOptions, channel chan string) (err error) {
func (o *Client) SendStream(msgs []*chat.ChatCompletionMessage, opts *domain.ChatOptions, channel chan domain.StreamUpdate) (err error) {
ctx := context.Background()
defer close(channel)
@@ -154,13 +154,30 @@ func (o *Client) SendStream(msgs []*chat.ChatCompletionMessage, opts *domain.Cha
for response, err := range stream {
if err != nil {
channel <- fmt.Sprintf("Error: %v\n", err)
channel <- domain.StreamUpdate{
Type: domain.StreamTypeError,
Content: fmt.Sprintf("Error: %v", err),
}
return err
}
text := o.extractTextFromResponse(response)
if text != "" {
channel <- text
channel <- domain.StreamUpdate{
Type: domain.StreamTypeContent,
Content: text,
}
}
if response.UsageMetadata != nil {
channel <- domain.StreamUpdate{
Type: domain.StreamTypeUsage,
Usage: &domain.UsageMetadata{
InputTokens: int(response.UsageMetadata.PromptTokenCount),
OutputTokens: int(response.UsageMetadata.CandidatesTokenCount),
TotalTokens: int(response.UsageMetadata.TotalTokenCount),
},
}
}
}

View File

@@ -87,13 +87,16 @@ func (c *Client) ListModels() ([]string, error) {
return models, nil
}
func (c *Client) SendStream(msgs []*chat.ChatCompletionMessage, opts *domain.ChatOptions, channel chan string) (err error) {
func (c *Client) SendStream(msgs []*chat.ChatCompletionMessage, opts *domain.ChatOptions, channel chan domain.StreamUpdate) (err error) {
url := fmt.Sprintf("%s/chat/completions", c.ApiUrl.Value)
payload := map[string]any{
"messages": msgs,
"model": opts.Model,
"stream": true, // Enable streaming
"stream_options": map[string]any{
"include_usage": true,
},
}
var jsonPayload []byte
@@ -144,7 +147,7 @@ func (c *Client) SendStream(msgs []*chat.ChatCompletionMessage, opts *domain.Cha
line = after
}
if string(line) == "[DONE]" {
if string(bytes.TrimSpace(line)) == "[DONE]" {
break
}
@@ -153,6 +156,24 @@ func (c *Client) SendStream(msgs []*chat.ChatCompletionMessage, opts *domain.Cha
continue
}
// Handle Usage
if usage, ok := result["usage"].(map[string]any); ok {
var metadata domain.UsageMetadata
if val, ok := usage["prompt_tokens"].(float64); ok {
metadata.InputTokens = int(val)
}
if val, ok := usage["completion_tokens"].(float64); ok {
metadata.OutputTokens = int(val)
}
if val, ok := usage["total_tokens"].(float64); ok {
metadata.TotalTokens = int(val)
}
channel <- domain.StreamUpdate{
Type: domain.StreamTypeUsage,
Usage: &metadata,
}
}
var choices []any
var ok bool
if choices, ok = result["choices"].([]any); !ok || len(choices) == 0 {
@@ -166,7 +187,10 @@ func (c *Client) SendStream(msgs []*chat.ChatCompletionMessage, opts *domain.Cha
var content string
if content, _ = delta["content"].(string); content != "" {
channel <- content
channel <- domain.StreamUpdate{
Type: domain.StreamTypeContent,
Content: content,
}
}
}

View File

@@ -106,7 +106,7 @@ func (o *Client) ListModels() (ret []string, err error) {
return
}
func (o *Client) SendStream(msgs []*chat.ChatCompletionMessage, opts *domain.ChatOptions, channel chan string) (err error) {
func (o *Client) SendStream(msgs []*chat.ChatCompletionMessage, opts *domain.ChatOptions, channel chan domain.StreamUpdate) (err error) {
ctx := context.Background()
var req ollamaapi.ChatRequest
@@ -115,7 +115,21 @@ func (o *Client) SendStream(msgs []*chat.ChatCompletionMessage, opts *domain.Cha
}
respFunc := func(resp ollamaapi.ChatResponse) (streamErr error) {
channel <- resp.Message.Content
channel <- domain.StreamUpdate{
Type: domain.StreamTypeContent,
Content: resp.Message.Content,
}
if resp.Done {
channel <- domain.StreamUpdate{
Type: domain.StreamTypeUsage,
Usage: &domain.UsageMetadata{
InputTokens: resp.PromptEvalCount,
OutputTokens: resp.EvalCount,
TotalTokens: resp.PromptEvalCount + resp.EvalCount,
},
}
}
return
}

View File

@@ -30,7 +30,7 @@ func (o *Client) sendChatCompletions(ctx context.Context, msgs []*chat.ChatCompl
// sendStreamChatCompletions sends a streaming request using the Chat Completions API
func (o *Client) sendStreamChatCompletions(
msgs []*chat.ChatCompletionMessage, opts *domain.ChatOptions, channel chan string,
msgs []*chat.ChatCompletionMessage, opts *domain.ChatOptions, channel chan domain.StreamUpdate,
) (err error) {
defer close(channel)
@@ -39,11 +39,28 @@ func (o *Client) sendStreamChatCompletions(
for stream.Next() {
chunk := stream.Current()
if len(chunk.Choices) > 0 && chunk.Choices[0].Delta.Content != "" {
channel <- chunk.Choices[0].Delta.Content
channel <- domain.StreamUpdate{
Type: domain.StreamTypeContent,
Content: chunk.Choices[0].Delta.Content,
}
}
if chunk.Usage.TotalTokens > 0 {
channel <- domain.StreamUpdate{
Type: domain.StreamTypeUsage,
Usage: &domain.UsageMetadata{
InputTokens: int(chunk.Usage.PromptTokens),
OutputTokens: int(chunk.Usage.CompletionTokens),
TotalTokens: int(chunk.Usage.TotalTokens),
},
}
}
}
if stream.Err() == nil {
channel <- "\n"
channel <- domain.StreamUpdate{
Type: domain.StreamTypeContent,
Content: "\n",
}
}
return stream.Err()
}
@@ -65,6 +82,9 @@ func (o *Client) buildChatCompletionParams(
ret = openai.ChatCompletionNewParams{
Model: shared.ChatModel(opts.Model),
Messages: messages,
StreamOptions: openai.ChatCompletionStreamOptionsParam{
IncludeUsage: openai.Bool(true),
},
}
if !opts.Raw {

View File

@@ -108,7 +108,7 @@ func (o *Client) ListModels() (ret []string, err error) {
}
func (o *Client) SendStream(
msgs []*chat.ChatCompletionMessage, opts *domain.ChatOptions, channel chan string,
msgs []*chat.ChatCompletionMessage, opts *domain.ChatOptions, channel chan domain.StreamUpdate,
) (err error) {
// Use Responses API for OpenAI, Chat Completions API for other providers
if o.supportsResponsesAPI() {
@@ -118,7 +118,7 @@ func (o *Client) SendStream(
}
func (o *Client) sendStreamResponses(
msgs []*chat.ChatCompletionMessage, opts *domain.ChatOptions, channel chan string,
msgs []*chat.ChatCompletionMessage, opts *domain.ChatOptions, channel chan domain.StreamUpdate,
) (err error) {
defer close(channel)
@@ -128,7 +128,10 @@ func (o *Client) sendStreamResponses(
event := stream.Current()
switch event.Type {
case string(constant.ResponseOutputTextDelta("").Default()):
channel <- event.AsResponseOutputTextDelta().Delta
channel <- domain.StreamUpdate{
Type: domain.StreamTypeContent,
Content: event.AsResponseOutputTextDelta().Delta,
}
case string(constant.ResponseOutputTextDone("").Default()):
// The Responses API sends the full text again in the
// final "done" event. Since we've already streamed all
@@ -138,7 +141,10 @@ func (o *Client) sendStreamResponses(
}
}
if stream.Err() == nil {
channel <- "\n"
channel <- domain.StreamUpdate{
Type: domain.StreamTypeContent,
Content: "\n",
}
}
return stream.Err()
}

View File

@@ -10,12 +10,20 @@ import (
"slices"
"sort"
"strings"
"sync"
debuglog "github.com/danielmiessler/fabric/internal/log"
openai "github.com/openai/openai-go"
)
// transcriptionResult holds the result of a single chunk transcription.
type transcriptionResult struct {
index int
text string
err error
}
// MaxAudioFileSize defines the maximum allowed size for audio uploads (25MB).
const MaxAudioFileSize int64 = 25 * 1024 * 1024
@@ -73,27 +81,56 @@ func (o *Client) TranscribeFile(ctx context.Context, filePath, model string, spl
files = []string{filePath}
}
var builder strings.Builder
resultsChan := make(chan transcriptionResult, len(files))
var wg sync.WaitGroup
for i, f := range files {
debuglog.Log("Using model %s to transcribe part %d (file name: %s)...\n", model, i+1, f)
var chunk *os.File
if chunk, err = os.Open(f); err != nil {
return "", err
}
params := openai.AudioTranscriptionNewParams{
File: chunk,
Model: openai.AudioModel(model),
}
var resp *openai.Transcription
resp, err = o.ApiClient.Audio.Transcriptions.New(ctx, params)
chunk.Close()
if err != nil {
return "", err
wg.Add(1)
go func(index int, filePath string) {
defer wg.Done()
debuglog.Log("Using model %s to transcribe part %d (file name: %s)...\n", model, index+1, filePath)
chunk, openErr := os.Open(filePath)
if openErr != nil {
resultsChan <- transcriptionResult{index: index, err: openErr}
return
}
defer chunk.Close()
params := openai.AudioTranscriptionNewParams{
File: chunk,
Model: openai.AudioModel(model),
}
resp, transcribeErr := o.ApiClient.Audio.Transcriptions.New(ctx, params)
if transcribeErr != nil {
resultsChan <- transcriptionResult{index: index, err: transcribeErr}
return
}
resultsChan <- transcriptionResult{index: index, text: resp.Text}
}(i, f)
}
wg.Wait()
close(resultsChan)
results := make([]transcriptionResult, 0, len(files))
for result := range resultsChan {
if result.err != nil {
return "", result.err
}
results = append(results, result)
}
sort.Slice(results, func(i, j int) bool {
return results[i].index < results[j].index
})
var builder strings.Builder
for i, result := range results {
if i > 0 {
builder.WriteString(" ")
}
builder.WriteString(resp.Text)
builder.WriteString(result.text)
}
return builder.String(), nil

View File

@@ -123,7 +123,7 @@ func (c *Client) Send(ctx context.Context, msgs []*chat.ChatCompletionMessage, o
return content.String(), nil
}
func (c *Client) SendStream(msgs []*chat.ChatCompletionMessage, opts *domain.ChatOptions, channel chan string) error {
func (c *Client) SendStream(msgs []*chat.ChatCompletionMessage, opts *domain.ChatOptions, channel chan domain.StreamUpdate) error {
if c.client == nil {
if err := c.Configure(); err != nil {
close(channel) // Ensure channel is closed on error
@@ -196,7 +196,21 @@ func (c *Client) SendStream(msgs []*chat.ChatCompletionMessage, opts *domain.Cha
content = resp.Choices[0].Message.Content
}
if content != "" {
channel <- content
channel <- domain.StreamUpdate{
Type: domain.StreamTypeContent,
Content: content,
}
}
}
if resp.Usage.TotalTokens != 0 {
channel <- domain.StreamUpdate{
Type: domain.StreamTypeUsage,
Usage: &domain.UsageMetadata{
InputTokens: int(resp.Usage.PromptTokens),
OutputTokens: int(resp.Usage.CompletionTokens),
TotalTokens: int(resp.Usage.TotalTokens),
},
}
}
}
@@ -205,9 +219,14 @@ func (c *Client) SendStream(msgs []*chat.ChatCompletionMessage, opts *domain.Cha
if lastResponse != nil {
citations := lastResponse.GetCitations()
if len(citations) > 0 {
channel <- "\n\n# CITATIONS\n\n"
var citationsText strings.Builder
citationsText.WriteString("\n\n# CITATIONS\n\n")
for i, citation := range citations {
channel <- fmt.Sprintf("- [%d] %s\n", i+1, citation)
citationsText.WriteString(fmt.Sprintf("- [%d] %s\n", i+1, citation))
}
channel <- domain.StreamUpdate{
Type: domain.StreamTypeContent,
Content: citationsText.String(),
}
}
}

View File

@@ -12,7 +12,7 @@ import (
type Vendor interface {
plugins.Plugin
ListModels() ([]string, error)
SendStream([]*chat.ChatCompletionMessage, *domain.ChatOptions, chan string) error
SendStream([]*chat.ChatCompletionMessage, *domain.ChatOptions, chan domain.StreamUpdate) error
Send(context.Context, []*chat.ChatCompletionMessage, *domain.ChatOptions) (string, error)
NeedsRawMode(modelName string) bool
}

View File

@@ -20,7 +20,7 @@ func (v *stubVendor) Configure() error { return nil }
func (v *stubVendor) Setup() error { return nil }
func (v *stubVendor) SetupFillEnvFileContent(*bytes.Buffer) {}
func (v *stubVendor) ListModels() ([]string, error) { return nil, nil }
func (v *stubVendor) SendStream([]*chat.ChatCompletionMessage, *domain.ChatOptions, chan string) error {
func (v *stubVendor) SendStream([]*chat.ChatCompletionMessage, *domain.ChatOptions, chan domain.StreamUpdate) error {
return nil
}
func (v *stubVendor) Send(context.Context, []*chat.ChatCompletionMessage, *domain.ChatOptions) (string, error) {

View File

@@ -107,7 +107,7 @@ func (c *Client) Send(ctx context.Context, msgs []*chat.ChatCompletionMessage, o
return strings.Join(textParts, ""), nil
}
func (c *Client) SendStream(msgs []*chat.ChatCompletionMessage, opts *domain.ChatOptions, channel chan string) error {
func (c *Client) SendStream(msgs []*chat.ChatCompletionMessage, opts *domain.ChatOptions, channel chan domain.StreamUpdate) error {
if c.client == nil {
close(channel)
return fmt.Errorf("VertexAI client not initialized")
@@ -133,8 +133,34 @@ func (c *Client) SendStream(msgs []*chat.ChatCompletionMessage, opts *domain.Cha
// Process stream
for stream.Next() {
event := stream.Current()
// Handle Content
if event.Delta.Text != "" {
channel <- event.Delta.Text
channel <- domain.StreamUpdate{
Type: domain.StreamTypeContent,
Content: event.Delta.Text,
}
}
// Handle Usage
if event.Message.Usage.InputTokens != 0 || event.Message.Usage.OutputTokens != 0 {
channel <- domain.StreamUpdate{
Type: domain.StreamTypeUsage,
Usage: &domain.UsageMetadata{
InputTokens: int(event.Message.Usage.InputTokens),
OutputTokens: int(event.Message.Usage.OutputTokens),
TotalTokens: int(event.Message.Usage.InputTokens + event.Message.Usage.OutputTokens),
},
}
} else if event.Usage.InputTokens != 0 || event.Usage.OutputTokens != 0 {
channel <- domain.StreamUpdate{
Type: domain.StreamTypeUsage,
Usage: &domain.UsageMetadata{
InputTokens: int(event.Usage.InputTokens),
OutputTokens: int(event.Usage.OutputTokens),
TotalTokens: int(event.Usage.InputTokens + event.Usage.OutputTokens),
},
}
}
}

View File

@@ -40,9 +40,10 @@ type ChatRequest struct {
}
type StreamResponse struct {
Type string `json:"type"` // "content", "error", "complete"
Format string `json:"format"` // "markdown", "mermaid", "plain"
Content string `json:"content"` // The actual content
Type string `json:"type"` // "content", "usage", "error", "complete"
Format string `json:"format,omitempty"` // "markdown", "mermaid", "plain"
Content string `json:"content,omitempty"`
Usage *domain.UsageMetadata `json:"usage,omitempty"`
}
func NewChatHandler(r *gin.Engine, registry *core.PluginRegistry, db *fsdb.Db) *ChatHandler {
@@ -98,7 +99,7 @@ func (h *ChatHandler) HandleChat(c *gin.Context) {
log.Printf("Processing prompt %d: Model=%s Pattern=%s Context=%s",
i+1, prompt.Model, prompt.PatternName, prompt.ContextName)
streamChan := make(chan string)
streamChan := make(chan domain.StreamUpdate)
go func(p PromptRequest) {
defer close(streamChan)
@@ -117,10 +118,10 @@ func (h *ChatHandler) HandleChat(c *gin.Context) {
}
}
chatter, err := h.registry.GetChatter(p.Model, 2048, p.Vendor, "", false, false)
chatter, err := h.registry.GetChatter(p.Model, 2048, p.Vendor, "", true, false)
if err != nil {
log.Printf("Error creating chatter: %v", err)
streamChan <- fmt.Sprintf("Error: %v", err)
streamChan <- domain.StreamUpdate{Type: domain.StreamTypeError, Content: fmt.Sprintf("Error: %v", err)}
return
}
@@ -144,49 +145,46 @@ func (h *ChatHandler) HandleChat(c *gin.Context) {
FrequencyPenalty: request.FrequencyPenalty,
PresencePenalty: request.PresencePenalty,
Thinking: request.Thinking,
Search: request.Search,
SearchLocation: request.SearchLocation,
UpdateChan: streamChan,
Quiet: true,
}
session, err := chatter.Send(chatReq, opts)
_, err = chatter.Send(chatReq, opts)
if err != nil {
log.Printf("Error from chatter.Send: %v", err)
streamChan <- fmt.Sprintf("Error: %v", err)
// Error already sent to streamChan via domain.StreamTypeError if occurred in Send loop
return
}
if session == nil {
log.Printf("No session returned from chatter.Send")
streamChan <- "Error: No response from model"
return
}
lastMsg := session.GetLastMessage()
if lastMsg != nil {
streamChan <- lastMsg.Content
} else {
log.Printf("No message content in session")
streamChan <- "Error: No response content"
}
}(prompt)
for content := range streamChan {
for update := range streamChan {
select {
case <-clientGone:
return
default:
var response StreamResponse
if strings.HasPrefix(content, "Error:") {
switch update.Type {
case domain.StreamTypeContent:
response = StreamResponse{
Type: "content",
Format: detectFormat(update.Content),
Content: update.Content,
}
case domain.StreamTypeUsage:
response = StreamResponse{
Type: "usage",
Usage: update.Usage,
}
case domain.StreamTypeError:
response = StreamResponse{
Type: "error",
Format: "plain",
Content: content,
}
} else {
response = StreamResponse{
Type: "content",
Format: detectFormat(content),
Content: content,
Content: update.Content,
}
}
if err := writeSSEResponse(c.Writer, response); err != nil {
log.Printf("Error writing response: %v", err)
return

View File

@@ -1 +1 @@
"1.4.366"
"1.4.373"