@shawnstack/quickforge 1.3.4 → 1.3.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (24) hide show
  1. package/README.md +348 -348
  2. package/dist/assets/{anthropic-BtDSt6Co.js → anthropic-67kzbrvt.js} +1 -1
  3. package/dist/assets/{azure-openai-responses-BiE5KBJr.js → azure-openai-responses-D2XquyCv.js} +1 -1
  4. package/dist/assets/{google-yYxzgw4O.js → google-CPViRPcU.js} +1 -1
  5. package/dist/assets/{google-gemini-cli-B8OJAX-P.js → google-gemini-cli-CNT4rz9y.js} +1 -1
  6. package/dist/assets/{google-vertex-Dmz1T75D.js → google-vertex-DOnrgFCc.js} +1 -1
  7. package/dist/assets/{index-BcbxYyM-.css → index-B0wkRg7T.css} +1 -1
  8. package/dist/assets/{index-Bc4Ghgsv.js → index-D3njc0Th.js} +454 -445
  9. package/dist/assets/{mistral-BJiBvfjD.js → mistral-Cz-xIri6.js} +1 -1
  10. package/dist/assets/{openai-codex-responses-rJJhq5x0.js → openai-codex-responses-CyA4Z3WI.js} +1 -1
  11. package/dist/assets/{openai-completions-CaCNPWQP.js → openai-completions-DSDeg9P2.js} +1 -1
  12. package/dist/assets/{openai-responses-D-zeSioM.js → openai-responses-jFa5zzTQ.js} +1 -1
  13. package/dist/assets/{openai-responses-shared-Bqg-VsV2.js → openai-responses-shared-BDx4vPct.js} +1 -1
  14. package/dist/index.html +2 -2
  15. package/node_modules/@aws-sdk/client-bedrock-runtime/dist-cjs/schemas/schemas_0.js +7 -4
  16. package/node_modules/@aws-sdk/client-bedrock-runtime/dist-es/schemas/schemas_0.js +7 -4
  17. package/node_modules/@aws-sdk/client-bedrock-runtime/package.json +2 -2
  18. package/node_modules/@aws-sdk/token-providers/package.json +1 -1
  19. package/package.json +1 -1
  20. package/server/agent-manager.mjs +2 -2
  21. package/server/ai-http-logger.mjs +208 -0
  22. package/server/conversation-compaction.mjs +3 -2
  23. package/server/index.mjs +2 -0
  24. package/server/utils/text-diff.mjs +215 -215
@@ -1,6 +1,6 @@
1
1
  import { promises as fs } from 'node:fs'
2
2
  import path from 'node:path'
3
- import { streamSimple } from '@mariozechner/pi-ai'
3
+ import { streamSimpleWithAiHttpLogging } from './ai-http-logger.mjs'
4
4
  import { cacheDir } from './storage.mjs'
5
5
 
6
6
  export const DEFAULT_COMPACT_KEEP_TURNS = 0
@@ -253,7 +253,7 @@ export async function compactConversation({ messages, model, thinkingLevel, getA
253
253
  const modelMaxTokens = Number(model.maxTokens) || 4096
254
254
  const maxTokens = Math.max(512, Math.min(modelMaxTokens, 4096))
255
255
  const apiKey = getApiKey ? await getApiKey(model.provider) : undefined
256
- const stream = streamSimple(
256
+ const stream = streamSimpleWithAiHttpLogging(
257
257
  model,
258
258
  {
259
259
  systemPrompt: COMPACT_SYSTEM_PROMPT,
@@ -266,6 +266,7 @@ export async function compactConversation({ messages, model, thinkingLevel, getA
266
266
  temperature: 0.2,
267
267
  reasoning: thinkingLevel === 'off' ? undefined : 'low',
268
268
  maxRetryDelayMs: 60000,
269
+ metadata: { quickforgePurpose: 'compact' },
269
270
  },
270
271
  )
271
272
 
package/server/index.mjs CHANGED
@@ -25,6 +25,7 @@ import { handleLanAccessApi, renderLanUnlockPage } from './routes/lan-access.mjs
25
25
  import { handleMcpApi } from './routes/mcp.mjs'
26
26
  import { serveStatic } from './routes/static.mjs'
27
27
  import { logger, flushLogger } from './utils/logger.mjs'
28
+ import { installAiHttpLogger } from './ai-http-logger.mjs'
28
29
  import { isLoopbackAddress, getLanUrls } from './utils/network.mjs'
29
30
  import { parseCookies } from './share-store.mjs'
30
31
  import { lanAccessCookieName, verifyLanAccessToken } from './lan-access-store.mjs'
@@ -50,6 +51,7 @@ const vitePort = Number(process.env.QUICKFORGE_VITE_PORT || 5176)
50
51
  let restartInProgress = false
51
52
 
52
53
  setDefaultWorkspaceRoot(process.env.QUICKFORGE_WORKSPACE_DIR || projectRoot)
54
+ installAiHttpLogger()
53
55
 
54
56
  function getRestartSupport() {
55
57
  return { supported: true, reason: null }
@@ -1,215 +1,215 @@
1
- const DEFAULT_CONTEXT_LINES = 3
2
- const DEFAULT_MAX_DIFF_CHARS = 60000
3
- const DEFAULT_MAX_DIFF_LINES = 1200
4
- const MAX_LCS_CELLS = 2_000_000
5
-
6
- function splitTextLines(text) {
7
- if (!text) return []
8
- const normalized = String(text).replace(/\r\n/g, '\n').replace(/\r/g, '\n')
9
- const lines = normalized.split('\n')
10
- if (lines.at(-1) === '') lines.pop()
11
- return lines
12
- }
13
-
14
- function countCommonPrefix(oldLines, newLines) {
15
- const limit = Math.min(oldLines.length, newLines.length)
16
- let index = 0
17
- while (index < limit && oldLines[index] === newLines[index]) index++
18
- return index
19
- }
20
-
21
- function countCommonSuffix(oldLines, newLines, prefixLength) {
22
- const oldRemaining = oldLines.length - prefixLength
23
- const newRemaining = newLines.length - prefixLength
24
- const limit = Math.min(oldRemaining, newRemaining)
25
- let count = 0
26
- while (count < limit && oldLines[oldLines.length - 1 - count] === newLines[newLines.length - 1 - count]) count++
27
- return count
28
- }
29
-
30
- function diffMiddleLines(oldLines, newLines) {
31
- const oldCount = oldLines.length
32
- const newCount = newLines.length
33
-
34
- if (oldCount === 0) return newLines.map((line) => ({ type: 'insert', line }))
35
- if (newCount === 0) return oldLines.map((line) => ({ type: 'delete', line }))
36
-
37
- if (oldCount * newCount > MAX_LCS_CELLS) {
38
- return [
39
- ...oldLines.map((line) => ({ type: 'delete', line })),
40
- ...newLines.map((line) => ({ type: 'insert', line })),
41
- ]
42
- }
43
-
44
- const dp = Array.from({ length: oldCount + 1 }, () => new Uint32Array(newCount + 1))
45
- for (let oldIndex = oldCount - 1; oldIndex >= 0; oldIndex--) {
46
- for (let newIndex = newCount - 1; newIndex >= 0; newIndex--) {
47
- dp[oldIndex][newIndex] = oldLines[oldIndex] === newLines[newIndex]
48
- ? dp[oldIndex + 1][newIndex + 1] + 1
49
- : Math.max(dp[oldIndex + 1][newIndex], dp[oldIndex][newIndex + 1])
50
- }
51
- }
52
-
53
- const operations = []
54
- let oldIndex = 0
55
- let newIndex = 0
56
- while (oldIndex < oldCount && newIndex < newCount) {
57
- if (oldLines[oldIndex] === newLines[newIndex]) {
58
- operations.push({ type: 'equal', line: oldLines[oldIndex] })
59
- oldIndex++
60
- newIndex++
61
- } else if (dp[oldIndex + 1][newIndex] >= dp[oldIndex][newIndex + 1]) {
62
- operations.push({ type: 'delete', line: oldLines[oldIndex] })
63
- oldIndex++
64
- } else {
65
- operations.push({ type: 'insert', line: newLines[newIndex] })
66
- newIndex++
67
- }
68
- }
69
-
70
- while (oldIndex < oldCount) {
71
- operations.push({ type: 'delete', line: oldLines[oldIndex] })
72
- oldIndex++
73
- }
74
- while (newIndex < newCount) {
75
- operations.push({ type: 'insert', line: newLines[newIndex] })
76
- newIndex++
77
- }
78
-
79
- return operations
80
- }
81
-
82
- function diffLineOperations(oldLines, newLines) {
83
- const prefixLength = countCommonPrefix(oldLines, newLines)
84
- const suffixLength = countCommonSuffix(oldLines, newLines, prefixLength)
85
- const oldMiddleEnd = oldLines.length - suffixLength
86
- const newMiddleEnd = newLines.length - suffixLength
87
-
88
- const operations = [
89
- ...oldLines.slice(0, prefixLength).map((line) => ({ type: 'equal', line })),
90
- ...diffMiddleLines(oldLines.slice(prefixLength, oldMiddleEnd), newLines.slice(prefixLength, newMiddleEnd)),
91
- ...oldLines.slice(oldMiddleEnd).map((line) => ({ type: 'equal', line })),
92
- ]
93
-
94
- let oldLine = 1
95
- let newLine = 1
96
- for (const operation of operations) {
97
- if (operation.type !== 'insert') {
98
- operation.oldLine = oldLine
99
- oldLine++
100
- } else {
101
- operation.oldLine = oldLine
102
- }
103
-
104
- if (operation.type !== 'delete') {
105
- operation.newLine = newLine
106
- newLine++
107
- } else {
108
- operation.newLine = newLine
109
- }
110
- }
111
-
112
- return operations
113
- }
114
-
115
- function changedOperationRanges(operations, contextLines) {
116
- const ranges = []
117
- let index = 0
118
-
119
- while (index < operations.length) {
120
- while (index < operations.length && operations[index].type === 'equal') index++
121
- if (index >= operations.length) break
122
-
123
- const changeStart = index
124
- while (index < operations.length && operations[index].type !== 'equal') index++
125
- const changeEnd = index - 1
126
- const start = Math.max(0, changeStart - contextLines)
127
- const end = Math.min(operations.length, changeEnd + contextLines + 1)
128
-
129
- const previous = ranges.at(-1)
130
- if (previous && start <= previous.end) {
131
- previous.end = end
132
- } else {
133
- ranges.push({ start, end })
134
- }
135
- }
136
-
137
- return ranges
138
- }
139
-
140
- function formatRange(start, count) {
141
- if (count === 0) return `${start},0`
142
- if (count === 1) return String(start)
143
- return `${start},${count}`
144
- }
145
-
146
- function hunkHeader(operations, oldLineCount) {
147
- const oldOperations = operations.filter((operation) => operation.type !== 'insert')
148
- const newOperations = operations.filter((operation) => operation.type !== 'delete')
149
- const firstOperation = operations[0]
150
- const oldStart = oldOperations[0]?.oldLine ?? (oldLineCount === 0 ? 0 : firstOperation?.oldLine ?? 1)
151
- const newStart = newOperations[0]?.newLine ?? (firstOperation?.newLine ?? 1)
152
-
153
- return `@@ -${formatRange(oldStart, oldOperations.length)} +${formatRange(newStart, newOperations.length)} @@`
154
- }
155
-
156
- function formatOperation(operation) {
157
- if (operation.type === 'insert') return `+${operation.line}`
158
- if (operation.type === 'delete') return `-${operation.line}`
159
- return ` ${operation.line}`
160
- }
161
-
162
- function truncateDiffText(text, maxChars, maxLines) {
163
- let truncated = false
164
- let output = text
165
-
166
- const lines = output.split('\n')
167
- if (lines.length > maxLines) {
168
- output = lines.slice(0, maxLines).join('\n')
169
- truncated = true
170
- }
171
-
172
- if (output.length > maxChars) {
173
- output = output.slice(0, maxChars)
174
- truncated = true
175
- }
176
-
177
- if (truncated) output = `${output}\n\n[diff truncated]`
178
- return { text: output, truncated }
179
- }
180
-
181
- export function createTextDiff(oldText, newText, relativePath, options = {}) {
182
- const oldLines = splitTextLines(oldText)
183
- const newLines = splitTextLines(newText)
184
- const operations = diffLineOperations(oldLines, newLines)
185
- const addedLines = operations.filter((operation) => operation.type === 'insert').length
186
- const removedLines = operations.filter((operation) => operation.type === 'delete').length
187
- const contextLines = Math.max(0, Number(options.contextLines ?? DEFAULT_CONTEXT_LINES))
188
- const ranges = changedOperationRanges(operations, contextLines)
189
- const oldLabel = options.oldExists === false ? '/dev/null' : `a/${relativePath}`
190
- const newLabel = `b/${relativePath}`
191
-
192
- const diffLines = ranges.length > 0 ? [`--- ${oldLabel}`, `+++ ${newLabel}`] : []
193
- for (const range of ranges) {
194
- const hunkOperations = operations.slice(range.start, range.end)
195
- diffLines.push(hunkHeader(hunkOperations, oldLines.length))
196
- diffLines.push(...hunkOperations.map(formatOperation))
197
- }
198
-
199
- const truncated = truncateDiffText(
200
- diffLines.join('\n'),
201
- Number(options.maxChars ?? DEFAULT_MAX_DIFF_CHARS),
202
- Number(options.maxLines ?? DEFAULT_MAX_DIFF_LINES),
203
- )
204
-
205
- return {
206
- format: 'unified',
207
- path: relativePath,
208
- addedLines,
209
- removedLines,
210
- oldLineCount: oldLines.length,
211
- newLineCount: newLines.length,
212
- truncated: truncated.truncated,
213
- text: truncated.text,
214
- }
215
- }
1
+ const DEFAULT_CONTEXT_LINES = 3
2
+ const DEFAULT_MAX_DIFF_CHARS = 60000
3
+ const DEFAULT_MAX_DIFF_LINES = 1200
4
+ const MAX_LCS_CELLS = 2_000_000
5
+
6
+ function splitTextLines(text) {
7
+ if (!text) return []
8
+ const normalized = String(text).replace(/\r\n/g, '\n').replace(/\r/g, '\n')
9
+ const lines = normalized.split('\n')
10
+ if (lines.at(-1) === '') lines.pop()
11
+ return lines
12
+ }
13
+
14
+ function countCommonPrefix(oldLines, newLines) {
15
+ const limit = Math.min(oldLines.length, newLines.length)
16
+ let index = 0
17
+ while (index < limit && oldLines[index] === newLines[index]) index++
18
+ return index
19
+ }
20
+
21
+ function countCommonSuffix(oldLines, newLines, prefixLength) {
22
+ const oldRemaining = oldLines.length - prefixLength
23
+ const newRemaining = newLines.length - prefixLength
24
+ const limit = Math.min(oldRemaining, newRemaining)
25
+ let count = 0
26
+ while (count < limit && oldLines[oldLines.length - 1 - count] === newLines[newLines.length - 1 - count]) count++
27
+ return count
28
+ }
29
+
30
+ function diffMiddleLines(oldLines, newLines) {
31
+ const oldCount = oldLines.length
32
+ const newCount = newLines.length
33
+
34
+ if (oldCount === 0) return newLines.map((line) => ({ type: 'insert', line }))
35
+ if (newCount === 0) return oldLines.map((line) => ({ type: 'delete', line }))
36
+
37
+ if (oldCount * newCount > MAX_LCS_CELLS) {
38
+ return [
39
+ ...oldLines.map((line) => ({ type: 'delete', line })),
40
+ ...newLines.map((line) => ({ type: 'insert', line })),
41
+ ]
42
+ }
43
+
44
+ const dp = Array.from({ length: oldCount + 1 }, () => new Uint32Array(newCount + 1))
45
+ for (let oldIndex = oldCount - 1; oldIndex >= 0; oldIndex--) {
46
+ for (let newIndex = newCount - 1; newIndex >= 0; newIndex--) {
47
+ dp[oldIndex][newIndex] = oldLines[oldIndex] === newLines[newIndex]
48
+ ? dp[oldIndex + 1][newIndex + 1] + 1
49
+ : Math.max(dp[oldIndex + 1][newIndex], dp[oldIndex][newIndex + 1])
50
+ }
51
+ }
52
+
53
+ const operations = []
54
+ let oldIndex = 0
55
+ let newIndex = 0
56
+ while (oldIndex < oldCount && newIndex < newCount) {
57
+ if (oldLines[oldIndex] === newLines[newIndex]) {
58
+ operations.push({ type: 'equal', line: oldLines[oldIndex] })
59
+ oldIndex++
60
+ newIndex++
61
+ } else if (dp[oldIndex + 1][newIndex] >= dp[oldIndex][newIndex + 1]) {
62
+ operations.push({ type: 'delete', line: oldLines[oldIndex] })
63
+ oldIndex++
64
+ } else {
65
+ operations.push({ type: 'insert', line: newLines[newIndex] })
66
+ newIndex++
67
+ }
68
+ }
69
+
70
+ while (oldIndex < oldCount) {
71
+ operations.push({ type: 'delete', line: oldLines[oldIndex] })
72
+ oldIndex++
73
+ }
74
+ while (newIndex < newCount) {
75
+ operations.push({ type: 'insert', line: newLines[newIndex] })
76
+ newIndex++
77
+ }
78
+
79
+ return operations
80
+ }
81
+
82
+ function diffLineOperations(oldLines, newLines) {
83
+ const prefixLength = countCommonPrefix(oldLines, newLines)
84
+ const suffixLength = countCommonSuffix(oldLines, newLines, prefixLength)
85
+ const oldMiddleEnd = oldLines.length - suffixLength
86
+ const newMiddleEnd = newLines.length - suffixLength
87
+
88
+ const operations = [
89
+ ...oldLines.slice(0, prefixLength).map((line) => ({ type: 'equal', line })),
90
+ ...diffMiddleLines(oldLines.slice(prefixLength, oldMiddleEnd), newLines.slice(prefixLength, newMiddleEnd)),
91
+ ...oldLines.slice(oldMiddleEnd).map((line) => ({ type: 'equal', line })),
92
+ ]
93
+
94
+ let oldLine = 1
95
+ let newLine = 1
96
+ for (const operation of operations) {
97
+ if (operation.type !== 'insert') {
98
+ operation.oldLine = oldLine
99
+ oldLine++
100
+ } else {
101
+ operation.oldLine = oldLine
102
+ }
103
+
104
+ if (operation.type !== 'delete') {
105
+ operation.newLine = newLine
106
+ newLine++
107
+ } else {
108
+ operation.newLine = newLine
109
+ }
110
+ }
111
+
112
+ return operations
113
+ }
114
+
115
+ function changedOperationRanges(operations, contextLines) {
116
+ const ranges = []
117
+ let index = 0
118
+
119
+ while (index < operations.length) {
120
+ while (index < operations.length && operations[index].type === 'equal') index++
121
+ if (index >= operations.length) break
122
+
123
+ const changeStart = index
124
+ while (index < operations.length && operations[index].type !== 'equal') index++
125
+ const changeEnd = index - 1
126
+ const start = Math.max(0, changeStart - contextLines)
127
+ const end = Math.min(operations.length, changeEnd + contextLines + 1)
128
+
129
+ const previous = ranges.at(-1)
130
+ if (previous && start <= previous.end) {
131
+ previous.end = end
132
+ } else {
133
+ ranges.push({ start, end })
134
+ }
135
+ }
136
+
137
+ return ranges
138
+ }
139
+
140
+ function formatRange(start, count) {
141
+ if (count === 0) return `${start},0`
142
+ if (count === 1) return String(start)
143
+ return `${start},${count}`
144
+ }
145
+
146
+ function hunkHeader(operations, oldLineCount) {
147
+ const oldOperations = operations.filter((operation) => operation.type !== 'insert')
148
+ const newOperations = operations.filter((operation) => operation.type !== 'delete')
149
+ const firstOperation = operations[0]
150
+ const oldStart = oldOperations[0]?.oldLine ?? (oldLineCount === 0 ? 0 : firstOperation?.oldLine ?? 1)
151
+ const newStart = newOperations[0]?.newLine ?? (firstOperation?.newLine ?? 1)
152
+
153
+ return `@@ -${formatRange(oldStart, oldOperations.length)} +${formatRange(newStart, newOperations.length)} @@`
154
+ }
155
+
156
+ function formatOperation(operation) {
157
+ if (operation.type === 'insert') return `+${operation.line}`
158
+ if (operation.type === 'delete') return `-${operation.line}`
159
+ return ` ${operation.line}`
160
+ }
161
+
162
+ function truncateDiffText(text, maxChars, maxLines) {
163
+ let truncated = false
164
+ let output = text
165
+
166
+ const lines = output.split('\n')
167
+ if (lines.length > maxLines) {
168
+ output = lines.slice(0, maxLines).join('\n')
169
+ truncated = true
170
+ }
171
+
172
+ if (output.length > maxChars) {
173
+ output = output.slice(0, maxChars)
174
+ truncated = true
175
+ }
176
+
177
+ if (truncated) output = `${output}\n\n[diff truncated]`
178
+ return { text: output, truncated }
179
+ }
180
+
181
+ export function createTextDiff(oldText, newText, relativePath, options = {}) {
182
+ const oldLines = splitTextLines(oldText)
183
+ const newLines = splitTextLines(newText)
184
+ const operations = diffLineOperations(oldLines, newLines)
185
+ const addedLines = operations.filter((operation) => operation.type === 'insert').length
186
+ const removedLines = operations.filter((operation) => operation.type === 'delete').length
187
+ const contextLines = Math.max(0, Number(options.contextLines ?? DEFAULT_CONTEXT_LINES))
188
+ const ranges = changedOperationRanges(operations, contextLines)
189
+ const oldLabel = options.oldExists === false ? '/dev/null' : `a/${relativePath}`
190
+ const newLabel = `b/${relativePath}`
191
+
192
+ const diffLines = ranges.length > 0 ? [`--- ${oldLabel}`, `+++ ${newLabel}`] : []
193
+ for (const range of ranges) {
194
+ const hunkOperations = operations.slice(range.start, range.end)
195
+ diffLines.push(hunkHeader(hunkOperations, oldLines.length))
196
+ diffLines.push(...hunkOperations.map(formatOperation))
197
+ }
198
+
199
+ const truncated = truncateDiffText(
200
+ diffLines.join('\n'),
201
+ Number(options.maxChars ?? DEFAULT_MAX_DIFF_CHARS),
202
+ Number(options.maxLines ?? DEFAULT_MAX_DIFF_LINES),
203
+ )
204
+
205
+ return {
206
+ format: 'unified',
207
+ path: relativePath,
208
+ addedLines,
209
+ removedLines,
210
+ oldLineCount: oldLines.length,
211
+ newLineCount: newLines.length,
212
+ truncated: truncated.truncated,
213
+ text: truncated.text,
214
+ }
215
+ }