picocode-core 0.9.156 → 0.9.157
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/src/agent.js +21 -1
- package/src/attachments.js +7 -0
- package/src/controller.js +10 -0
- package/src/derive.js +1 -1
package/package.json
CHANGED
package/src/agent.js
CHANGED
|
@@ -1,11 +1,31 @@
|
|
|
1
1
|
import { readFile } from 'node:fs/promises'
|
|
2
|
+
import { execFile } from 'node:child_process'
|
|
3
|
+
import { promisify } from 'node:util'
|
|
2
4
|
import { compose, scope, model, noToolsCalled, Inherit, getText } from '@prsm/ai'
|
|
3
|
-
import { fileLabel, mediaTypeFor, selectionLabel } from './attachments.js'
|
|
5
|
+
import { commitLabel, fileLabel, mediaTypeFor, selectionLabel } from './attachments.js'
|
|
6
|
+
|
|
7
|
+
const exec = promisify(execFile)
|
|
8
|
+
const COMMIT_PATCH_LIMIT = 60 * 1024
|
|
9
|
+
|
|
10
|
+
// a commit part carries the commit itself: its header, stat, and patch,
|
|
11
|
+
// cut off past the limit, so the model reads what happened without a
|
|
12
|
+
// git call of its own
|
|
13
|
+
export async function hydrateCommit(part) {
|
|
14
|
+
const head = commitLabel(part)
|
|
15
|
+
try {
|
|
16
|
+
const { stdout } = await exec('git', ['--no-optional-locks', 'show', '--stat', '--patch', '--format=%H%nAuthor: %an <%ae>%nDate: %ad%n%n%B', part.hash], { cwd: part.root, maxBuffer: COMMIT_PATCH_LIMIT * 4 })
|
|
17
|
+
const body = stdout.length > COMMIT_PATCH_LIMIT ? `${stdout.slice(0, COMMIT_PATCH_LIMIT)}\n[patch truncated at ${COMMIT_PATCH_LIMIT} bytes]` : stdout
|
|
18
|
+
return { type: 'text', text: `${head}\n${body.trimEnd()}\n[/commit]` }
|
|
19
|
+
} catch {
|
|
20
|
+
return { type: 'text', text: `${head}\n[commit unavailable]\n[/commit]` }
|
|
21
|
+
}
|
|
22
|
+
}
|
|
4
23
|
|
|
5
24
|
// file parts reach the model as a labelled path; it reads them itself
|
|
6
25
|
async function hydratePart(part) {
|
|
7
26
|
if (part.type === 'file') return { type: 'text', text: fileLabel(part.path) }
|
|
8
27
|
if (part.type === 'selection') return { type: 'text', text: selectionLabel(part) }
|
|
28
|
+
if (part.type === 'commit') return hydrateCommit(part)
|
|
9
29
|
if (part.type !== 'image' || part.source?.kind !== 'path') return part
|
|
10
30
|
const mediaType = part.source.mediaType || mediaTypeFor(part.source.path)
|
|
11
31
|
if (!mediaType) return { type: 'text', text: `[image unavailable: ${part.source.path}]` }
|
package/src/attachments.js
CHANGED
|
@@ -72,6 +72,7 @@ export function splitTextByImagePaths(text, exists = existsSync) {
|
|
|
72
72
|
|
|
73
73
|
export const fileLabel = (path) => `[file: ${path}]`
|
|
74
74
|
export const selectionLabel = (part) => `[selection: ${part.path}:${part.fromLine}-${part.toLine}]\n${part.text}\n[/selection]`
|
|
75
|
+
export const commitLabel = (part) => `[commit: ${part.hash.slice(0, 7)} ${JSON.stringify(part.subject ?? '')}]`
|
|
75
76
|
|
|
76
77
|
// [Image #n] and [File #n] placeholders in the composed text stand for
|
|
77
78
|
// attachments; they become image, file, or selection parts of the user message
|
|
@@ -86,6 +87,8 @@ export function buildUserContent(text, attachments) {
|
|
|
86
87
|
if (before) parts.push({ type: 'text', text: before })
|
|
87
88
|
parts.push(attachment.kind === 'selection'
|
|
88
89
|
? { type: 'selection', path: attachment.path, text: attachment.text, fromLine: attachment.fromLine, toLine: attachment.toLine, ...(Number.isInteger(attachment.fromColumn) ? { fromColumn: attachment.fromColumn } : {}), ...(Number.isInteger(attachment.toColumn) ? { toColumn: attachment.toColumn } : {}) }
|
|
90
|
+
: attachment.kind === 'commit'
|
|
91
|
+
? { type: 'commit', hash: attachment.hash, subject: attachment.subject, root: attachment.root }
|
|
89
92
|
: attachment.kind === 'file'
|
|
90
93
|
? { type: 'file', path: attachment.path }
|
|
91
94
|
: { type: 'image', source: { kind: 'path', path: attachment.path, mediaType: attachment.mediaType } })
|
|
@@ -128,6 +131,10 @@ export function inputTextFromContent(content, { attachments, nextId }) {
|
|
|
128
131
|
const placeholder = `[File #${nextId()}]`
|
|
129
132
|
attachments.set(placeholder, { path: part.path, kind: 'file' })
|
|
130
133
|
out += placeholder
|
|
134
|
+
} else if (part.type === 'commit' && part.hash) {
|
|
135
|
+
const placeholder = `[File #${nextId()}]`
|
|
136
|
+
attachments.set(placeholder, { hash: part.hash, subject: part.subject, root: part.root, kind: 'commit' })
|
|
137
|
+
out += placeholder
|
|
131
138
|
} else if (part.type === 'selection' && part.path) {
|
|
132
139
|
const placeholder = `[File #${nextId()}]`
|
|
133
140
|
attachments.set(placeholder, { path: part.path, text: part.text, fromLine: part.fromLine, toLine: part.toLine, ...(Number.isInteger(part.fromColumn) ? { fromColumn: part.fromColumn } : {}), ...(Number.isInteger(part.toColumn) ? { toColumn: part.toColumn } : {}), kind: 'selection' })
|
package/src/controller.js
CHANGED
|
@@ -879,6 +879,15 @@ export function createController({ boot }) {
|
|
|
879
879
|
return placeholder
|
|
880
880
|
}
|
|
881
881
|
|
|
882
|
+
// a commit of the session's repository; the model receives the commit
|
|
883
|
+
// itself when the message is sent
|
|
884
|
+
function attachCommit({ hash, subject = '' }) {
|
|
885
|
+
if (!/^[0-9a-f]{7,40}$/i.test(String(hash ?? ''))) return null
|
|
886
|
+
const placeholder = `[File #${++state.imageCount}]`
|
|
887
|
+
state.attachments.set(placeholder, { hash, subject: String(subject), root: boot.root, kind: 'commit' })
|
|
888
|
+
return placeholder
|
|
889
|
+
}
|
|
890
|
+
|
|
882
891
|
// a project-relative pick: images attach as images, anything else as a
|
|
883
892
|
// file reference
|
|
884
893
|
function attachProjectFile(file) {
|
|
@@ -1374,6 +1383,7 @@ export function createController({ boot }) {
|
|
|
1374
1383
|
attachImage,
|
|
1375
1384
|
attachFile,
|
|
1376
1385
|
attachSelection,
|
|
1386
|
+
attachCommit,
|
|
1377
1387
|
attachProjectFile,
|
|
1378
1388
|
detachImage,
|
|
1379
1389
|
costSummary,
|
package/src/derive.js
CHANGED
|
@@ -101,7 +101,7 @@ function foldMessage(state, event) {
|
|
|
101
101
|
if (message.role === 'user') {
|
|
102
102
|
const text = Array.isArray(message.content)
|
|
103
103
|
? message.content
|
|
104
|
-
.map((p) => (p.type === 'text' ? p.text : p.type === 'file' ? `[file: ${p.path}]` : p.type === 'selection' ? `[selection: ${p.path}:${p.fromLine}-${p.toLine}]` : `[image: ${String(p.source?.path || '').split('/').pop() || 'attached'}]`))
|
|
104
|
+
.map((p) => (p.type === 'text' ? p.text : p.type === 'file' ? `[file: ${p.path}]` : p.type === 'selection' ? `[selection: ${p.path}:${p.fromLine}-${p.toLine}]` : p.type === 'commit' ? `[commit: ${String(p.hash).slice(0, 7)}]` : `[image: ${String(p.source?.path || '').split('/').pop() || 'attached'}]`))
|
|
105
105
|
.join('')
|
|
106
106
|
: String(message.content)
|
|
107
107
|
state.transcript.push({ ...base, kind: 'user', text, content: message.content, ...(event.data.origin ? { origin: event.data.origin } : {}) })
|