think-inc 0.1.0
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/LICENSE +21 -0
- package/README.md +69 -0
- package/dist/index.d.ts +13 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +5 -0
- package/dist/index.js.map +1 -0
- package/dist/logger.d.ts +3 -0
- package/dist/logger.d.ts.map +1 -0
- package/dist/logger.js +61 -0
- package/dist/logger.js.map +1 -0
- package/dist/parser.d.ts +39 -0
- package/dist/parser.d.ts.map +1 -0
- package/dist/parser.js +159 -0
- package/dist/parser.js.map +1 -0
- package/dist/transformer.d.ts +34 -0
- package/dist/transformer.d.ts.map +1 -0
- package/dist/transformer.js +178 -0
- package/dist/transformer.js.map +1 -0
- package/dist/types.d.ts +58 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +36 -0
- package/dist/types.js.map +1 -0
- package/package.json +44 -0
- package/src/index.ts +155 -0
- package/src/logger.ts +56 -0
- package/tsconfig.json +26 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 whpthomas
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
# Think-Inc OpenCode Plugin
|
|
2
|
+
|
|
3
|
+
[](https://www.npmjs.com/package/@whpthomas/think-inc)
|
|
4
|
+
[](https://opensource.org/licenses/MIT)
|
|
5
|
+
|
|
6
|
+
## What Is This?
|
|
7
|
+
|
|
8
|
+
Think-Inc is an simple OpenCode plugin that injects thinking traces back into the message context. It captures the reasoning parts as they stream in, stores them temporarily, then injects them back into message context so the model can see its thinking process.
|
|
9
|
+
|
|
10
|
+
## Why Use This?
|
|
11
|
+
|
|
12
|
+
To improve model self efficacy when workflows spawn new contexts as a first class construct:
|
|
13
|
+
|
|
14
|
+
- Ralph Loops
|
|
15
|
+
- Research -> Plan -> Implement (RPI) Workflows
|
|
16
|
+
- Questions -> Research -> Design -> Structure -> Plan -> Implement (QRSPI) Workflows
|
|
17
|
+
- Acceptance Criteria -> Implement <-> Validate (GAN) Loops
|
|
18
|
+
|
|
19
|
+
## How It Works
|
|
20
|
+
|
|
21
|
+
The plugin works in three stages:
|
|
22
|
+
|
|
23
|
+
1. **Capture**: Listens for `message.part.updated` events and accumulates reasoning chunks
|
|
24
|
+
2. **Store**: Maintains session-based storage for reasoning data across streaming events
|
|
25
|
+
3. **Inject**: Uses the `experimental.chat.messages.transform` hook to prepend reasoning blocks to messages
|
|
26
|
+
|
|
27
|
+
## Quick Start
|
|
28
|
+
|
|
29
|
+
### Installation
|
|
30
|
+
|
|
31
|
+
```bash
|
|
32
|
+
npm install @whpthomas/think-inc
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
### Usage
|
|
36
|
+
|
|
37
|
+
Create a plugin configuration file in your `.opencode/plugins/` directory:
|
|
38
|
+
|
|
39
|
+
```javascript
|
|
40
|
+
// .opencode/plugins/think-inc.js
|
|
41
|
+
import thinkIncPlugin from '@whpthomas/think-inc';
|
|
42
|
+
export default thinkIncPlugin;
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
### AGENTS.md
|
|
46
|
+
|
|
47
|
+
Include the following section in AGENTS.md
|
|
48
|
+
|
|
49
|
+
```markdown
|
|
50
|
+
## Reasoning
|
|
51
|
+
|
|
52
|
+
The Think-Inc OpenCode plugin is providing thinking traces inside `<reasoning>` blocks in your context. It provides transparency to understand your thinking process and reduce repetition. Follow these guidelines to reduce context duplication:
|
|
53
|
+
|
|
54
|
+
- Assume the reader has seen reasoning
|
|
55
|
+
- Keep final response concise and action-oriented
|
|
56
|
+
- Focus on deliverables, decisions, and next steps
|
|
57
|
+
- Don't repeat reasoning content in response
|
|
58
|
+
- Don't provide verbose explanations when reasoning covers thinking
|
|
59
|
+
- Don't add unnecessary preamble or restatement of the approach
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
## License
|
|
63
|
+
|
|
64
|
+
MIT License - see LICENSE file for details
|
|
65
|
+
|
|
66
|
+
## Authors
|
|
67
|
+
|
|
68
|
+
- [@whpthomas](https://github.com/whpthomas) - Concept, logic and troubleshooting
|
|
69
|
+
- Qwen3.5 122B hybrid int4fp8 - Research and coding
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Think-Inc Plugin - Reasoning Block Interceptor
|
|
3
|
+
*
|
|
4
|
+
* Intercepts reasoning parts from vllm responses and transforms them into
|
|
5
|
+
* visible, formatted conversation content.
|
|
6
|
+
*/
|
|
7
|
+
import type { Plugin } from '@opencode-ai/plugin';
|
|
8
|
+
/**
|
|
9
|
+
* Main plugin implementation
|
|
10
|
+
*/
|
|
11
|
+
export declare const thoughtsIncPlugin: Plugin;
|
|
12
|
+
export default thoughtsIncPlugin;
|
|
13
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,EAAE,MAAM,EAAsB,MAAM,qBAAqB,CAAC;AA0BtE;;GAEG;AACH,eAAO,MAAM,iBAAiB,EAAE,MAqE/B,CAAC;AAEF,eAAe,iBAAiB,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import*as i from"fs";import*as d from"path";var k=d.join(process.cwd(),".logs"),L=d.join(k,"think-inc.log"),n=null,W=!1;function F(){if(W)return!0;try{if(!i.existsSync(k))i.mkdirSync(k,{recursive:!0});return n=i.createWriteStream(L,{flags:"a"}),W=!0,!0}catch{return!1}}function $(){return new Date().toISOString()}function u(...S){if(!F()||!n)return;let j=$(),x=S.map((l)=>typeof l==="object"?JSON.stringify(l,null,2):String(l)).join(" ");n.write(`[${j}] ${x}
|
|
2
|
+
`)}function G(){if(!n)return Promise.resolve();return new Promise((S)=>{n.end(()=>{n=null,W=!1,S()})})}function _(){if(!F())return;n?.close(),i.writeFileSync(L,""),n=i.createWriteStream(L,{flags:"a"})}var w=new Map,E=async(S)=>{return _(),u("=== Think-Inc Plugin Initialized ==="),{event:async({event:x})=>{if(x.type!=="message.part.updated")return;let r=x.properties.part;if(r.type!=="reasoning")return;if(!r.text?.trim())return;let{messageID:t,sessionID:f}=r,O=r.text.slice(0,35).replace(/\n/g,"\\n");if(u(`[CAPTURE] "${O}..." ${r.text.length} chars`),!w.has(f))w.set(f,{reasoning:new Map,complete:new Set});let c=w.get(f);if(!c.reasoning.has(t))c.reasoning.set(t,[]);c.reasoning.get(t).push(r.text)},"experimental.chat.messages.transform":async(x,l)=>{u(`[TRANSFORM] Processing ${l.messages.length} messages`);for(let r=l.messages.length-1;r>=0;r--){let t=l.messages[r],f=t.info.id,O=t.info.sessionID,c=w.get(O);if(!c)continue;if(!c.reasoning.has(f))continue;if(c.complete.has(f)){u(`[TRANSFORM] Message ${f} already completed`);break}let h=c.reasoning.get(f).filter((y)=>y.trim()).join(`
|
|
3
|
+
`);if(h.trim()){let y={type:"text",text:`<reasoning>
|
|
4
|
+
${h}
|
|
5
|
+
</reasoning>`,synthetic:!0,id:`reasoning-${f}`,sessionID:O,messageID:f};t.parts.unshift(y),u(`[INJECT] ${h.length} chars ${f} ${O}`)}c.complete.add(f),c.reasoning.delete(f);for(let[y,z]of w)if(z.reasoning.size===0&&z.complete.size>0)w.delete(y),u(`[CLEANUP] Session ${y} cleaned up`),await G();break}return l}}},T=E;export{E as thoughtsIncPlugin,T as default};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
package/dist/logger.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"logger.d.ts","sourceRoot":"","sources":["../src/logger.ts"],"names":[],"mappings":"AAoDA,wBAAgB,GAAG,CAAC,GAAG,IAAI,EAAE,OAAO,EAAE,QASrC;AAED,wBAAgB,QAAQ,SAGvB"}
|
package/dist/logger.js
ADDED
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import * as fs from "fs";
|
|
2
|
+
import * as path from "path";
|
|
3
|
+
const LOG_DIR = path.join(process.cwd(), ".logs");
|
|
4
|
+
const LOG_FILE = path.join(LOG_DIR, "think-inc.log");
|
|
5
|
+
const WRITE_INTERVAL_MS = 100;
|
|
6
|
+
let logBuffer = [];
|
|
7
|
+
let writeScheduled = false;
|
|
8
|
+
let initialized = false;
|
|
9
|
+
function ensureInitialized() {
|
|
10
|
+
if (initialized)
|
|
11
|
+
return true;
|
|
12
|
+
try {
|
|
13
|
+
if (!fs.existsSync(LOG_DIR)) {
|
|
14
|
+
fs.mkdirSync(LOG_DIR, { recursive: true });
|
|
15
|
+
}
|
|
16
|
+
fs.writeFileSync(LOG_FILE, "");
|
|
17
|
+
initialized = true;
|
|
18
|
+
return true;
|
|
19
|
+
}
|
|
20
|
+
catch {
|
|
21
|
+
return false;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
function formatTimestamp() {
|
|
25
|
+
return new Date().toISOString();
|
|
26
|
+
}
|
|
27
|
+
async function flushLogs() {
|
|
28
|
+
if (logBuffer.length === 0) {
|
|
29
|
+
writeScheduled = false;
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
32
|
+
const toWrite = logBuffer.join("");
|
|
33
|
+
logBuffer = [];
|
|
34
|
+
writeScheduled = false;
|
|
35
|
+
try {
|
|
36
|
+
await fs.promises.appendFile(LOG_FILE, toWrite);
|
|
37
|
+
}
|
|
38
|
+
catch { }
|
|
39
|
+
}
|
|
40
|
+
function scheduleFlush() {
|
|
41
|
+
if (!writeScheduled) {
|
|
42
|
+
writeScheduled = true;
|
|
43
|
+
setTimeout(flushLogs, WRITE_INTERVAL_MS);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
export function log(...args) {
|
|
47
|
+
if (!ensureInitialized())
|
|
48
|
+
return;
|
|
49
|
+
const timestamp = formatTimestamp();
|
|
50
|
+
const message = args
|
|
51
|
+
.map(a => (typeof a === "object" ? JSON.stringify(a, null, 2) : String(a)))
|
|
52
|
+
.join(" ");
|
|
53
|
+
logBuffer.push(`[${timestamp}] ${message}\n`);
|
|
54
|
+
scheduleFlush();
|
|
55
|
+
}
|
|
56
|
+
export function clearLog() {
|
|
57
|
+
if (!ensureInitialized())
|
|
58
|
+
return;
|
|
59
|
+
fs.writeFileSync(LOG_FILE, "");
|
|
60
|
+
}
|
|
61
|
+
//# sourceMappingURL=logger.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"logger.js","sourceRoot":"","sources":["../src/logger.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,IAAI,CAAC;AACzB,OAAO,KAAK,IAAI,MAAM,MAAM,CAAC;AAE7B,MAAM,OAAO,GAAG,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,GAAG,EAAE,EAAE,OAAO,CAAC,CAAC;AAClD,MAAM,QAAQ,GAAG,IAAI,CAAC,IAAI,CAAC,OAAO,EAAE,eAAe,CAAC,CAAC;AACrD,MAAM,iBAAiB,GAAG,GAAG,CAAC;AAE9B,IAAI,SAAS,GAAa,EAAE,CAAC;AAC7B,IAAI,cAAc,GAAG,KAAK,CAAC;AAC3B,IAAI,WAAW,GAAG,KAAK,CAAC;AAExB,SAAS,iBAAiB;IACxB,IAAI,WAAW;QAAE,OAAO,IAAI,CAAC;IAE7B,IAAI,CAAC;QACH,IAAI,CAAC,EAAE,CAAC,UAAU,CAAC,OAAO,CAAC,EAAE,CAAC;YAC5B,EAAE,CAAC,SAAS,CAAC,OAAO,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;QAC7C,CAAC;QACD,EAAE,CAAC,aAAa,CAAC,QAAQ,EAAE,EAAE,CAAC,CAAC;QAC/B,WAAW,GAAG,IAAI,CAAC;QACnB,OAAO,IAAI,CAAC;IACd,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,KAAK,CAAC;IACf,CAAC;AACH,CAAC;AAED,SAAS,eAAe;IACtB,OAAO,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC;AAClC,CAAC;AAED,KAAK,UAAU,SAAS;IACtB,IAAI,SAAS,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QAC3B,cAAc,GAAG,KAAK,CAAC;QACvB,OAAO;IACT,CAAC;IAED,MAAM,OAAO,GAAG,SAAS,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;IACnC,SAAS,GAAG,EAAE,CAAC;IACf,cAAc,GAAG,KAAK,CAAC;IAEvB,IAAI,CAAC;QACH,MAAM,EAAE,CAAC,QAAQ,CAAC,UAAU,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAC;IAClD,CAAC;IAAC,MAAM,CAAC,CAAC,CAAC;AACb,CAAC;AAED,SAAS,aAAa;IACpB,IAAI,CAAC,cAAc,EAAE,CAAC;QACpB,cAAc,GAAG,IAAI,CAAC;QACtB,UAAU,CAAC,SAAS,EAAE,iBAAiB,CAAC,CAAC;IAC3C,CAAC;AACH,CAAC;AAED,MAAM,UAAU,GAAG,CAAC,GAAG,IAAe;IACpC,IAAI,CAAC,iBAAiB,EAAE;QAAE,OAAO;IAEjC,MAAM,SAAS,GAAG,eAAe,EAAE,CAAC;IACpC,MAAM,OAAO,GAAG,IAAI;SACjB,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,OAAO,CAAC,KAAK,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC;SAC1E,IAAI,CAAC,GAAG,CAAC,CAAC;IACb,SAAS,CAAC,IAAI,CAAC,IAAI,SAAS,KAAK,OAAO,IAAI,CAAC,CAAC;IAC9C,aAAa,EAAE,CAAC;AAClB,CAAC;AAED,MAAM,UAAU,QAAQ;IACtB,IAAI,CAAC,iBAAiB,EAAE;QAAE,OAAO;IACjC,EAAE,CAAC,aAAa,CAAC,QAAQ,EAAE,EAAE,CAAC,CAAC;AACjC,CAAC"}
|
package/dist/parser.d.ts
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Parser for Reasoning Blocks
|
|
3
|
+
*
|
|
4
|
+
* Detects and extracts reasoning blocks from LLM responses.
|
|
5
|
+
* Handles multiple tag formats, edge cases, and malformed input gracefully.
|
|
6
|
+
*/
|
|
7
|
+
import type { ReasoningBlock } from './types.js';
|
|
8
|
+
/**
|
|
9
|
+
* Parse all reasoning blocks from content
|
|
10
|
+
* @param content - Raw content from LLM response
|
|
11
|
+
* @returns Array of ReasoningBlock objects sorted by position
|
|
12
|
+
*/
|
|
13
|
+
export declare function parseReasoning(content: string): ReasoningBlock[];
|
|
14
|
+
/**
|
|
15
|
+
* Parse a single reasoning block at given position
|
|
16
|
+
* @param content - Raw content
|
|
17
|
+
* @param startTag - Opening tag to look for (default: '<thoughts>')
|
|
18
|
+
* @returns ReasoningBlock or null if not found
|
|
19
|
+
*/
|
|
20
|
+
export declare function parseSingleReasoning(content: string, startTag?: string): ReasoningBlock | null;
|
|
21
|
+
/**
|
|
22
|
+
* Quick check if content contains any reasoning blocks
|
|
23
|
+
* @param content - Raw content
|
|
24
|
+
* @returns boolean indicating if any reasoning blocks are present
|
|
25
|
+
*/
|
|
26
|
+
export declare function hasReasoning(content: string): boolean;
|
|
27
|
+
/**
|
|
28
|
+
* Extract reasoning content only (without tags)
|
|
29
|
+
* @param content - Raw content
|
|
30
|
+
* @returns Concatenated reasoning content from all blocks
|
|
31
|
+
*/
|
|
32
|
+
export declare function extractReasoningContent(content: string): string;
|
|
33
|
+
/**
|
|
34
|
+
* Remove all reasoning blocks from content
|
|
35
|
+
* @param content - Raw content
|
|
36
|
+
* @returns Content with reasoning blocks removed
|
|
37
|
+
*/
|
|
38
|
+
export declare function removeReasoningBlocks(content: string): string;
|
|
39
|
+
//# sourceMappingURL=parser.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"parser.d.ts","sourceRoot":"","sources":["../src/parser.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,EAAE,cAAc,EAAgB,MAAM,YAAY,CAAC;AAG/D;;;;GAIG;AACH,wBAAgB,cAAc,CAAC,OAAO,EAAE,MAAM,GAAG,cAAc,EAAE,CA6ChE;AAwBD;;;;;GAKG;AACH,wBAAgB,oBAAoB,CAClC,OAAO,EAAE,MAAM,EACf,QAAQ,GAAE,MAAqB,GAC9B,cAAc,GAAG,IAAI,CAsCvB;AAED;;;;GAIG;AACH,wBAAgB,YAAY,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAcrD;AAED;;;;GAIG;AACH,wBAAgB,uBAAuB,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAQ/D;AAED;;;;GAIG;AACH,wBAAgB,qBAAqB,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAkB7D"}
|
package/dist/parser.js
ADDED
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Parser for Reasoning Blocks
|
|
3
|
+
*
|
|
4
|
+
* Detects and extracts reasoning blocks from LLM responses.
|
|
5
|
+
* Handles multiple tag formats, edge cases, and malformed input gracefully.
|
|
6
|
+
*/
|
|
7
|
+
import { DEFAULT_PATTERNS } from './types.js';
|
|
8
|
+
/**
|
|
9
|
+
* Parse all reasoning blocks from content
|
|
10
|
+
* @param content - Raw content from LLM response
|
|
11
|
+
* @returns Array of ReasoningBlock objects sorted by position
|
|
12
|
+
*/
|
|
13
|
+
export function parseReasoning(content) {
|
|
14
|
+
if (!content || typeof content !== 'string') {
|
|
15
|
+
return [];
|
|
16
|
+
}
|
|
17
|
+
const blocks = [];
|
|
18
|
+
try {
|
|
19
|
+
// Iterate through each pattern
|
|
20
|
+
for (const patternConfig of DEFAULT_PATTERNS) {
|
|
21
|
+
const pattern = patternConfig.pattern;
|
|
22
|
+
// Reset regex lastIndex to ensure fresh matching
|
|
23
|
+
pattern.lastIndex = 0;
|
|
24
|
+
const matches = [...content.matchAll(pattern)];
|
|
25
|
+
for (const match of matches) {
|
|
26
|
+
const fullMatch = match[0];
|
|
27
|
+
const content = match[1] ?? '';
|
|
28
|
+
// Extract actual tags from the match
|
|
29
|
+
const firstBracket = fullMatch.indexOf('>');
|
|
30
|
+
const lastBracket = fullMatch.lastIndexOf('<');
|
|
31
|
+
const startTag = fullMatch.substring(0, firstBracket + 1);
|
|
32
|
+
const endTag = fullMatch.substring(lastBracket);
|
|
33
|
+
blocks.push({
|
|
34
|
+
content: content.trim(),
|
|
35
|
+
startTag,
|
|
36
|
+
endTag,
|
|
37
|
+
startPosition: match.index,
|
|
38
|
+
endPosition: match.index + fullMatch.length
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
catch (error) {
|
|
44
|
+
console.error('[Parser] Error parsing:', error);
|
|
45
|
+
return [];
|
|
46
|
+
}
|
|
47
|
+
// Remove duplicates and sort by position
|
|
48
|
+
const uniqueBlocks = removeDuplicateBlocks(blocks);
|
|
49
|
+
return uniqueBlocks.sort((a, b) => a.startPosition - b.startPosition);
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Remove duplicate blocks that may be detected by multiple patterns
|
|
53
|
+
*/
|
|
54
|
+
function removeDuplicateBlocks(blocks) {
|
|
55
|
+
if (blocks.length <= 1) {
|
|
56
|
+
return blocks;
|
|
57
|
+
}
|
|
58
|
+
const seen = new Set();
|
|
59
|
+
const result = [];
|
|
60
|
+
for (const block of blocks) {
|
|
61
|
+
const key = `${block.startPosition}-${block.endPosition}`;
|
|
62
|
+
if (!seen.has(key)) {
|
|
63
|
+
seen.add(key);
|
|
64
|
+
result.push(block);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
return result;
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* Parse a single reasoning block at given position
|
|
71
|
+
* @param content - Raw content
|
|
72
|
+
* @param startTag - Opening tag to look for (default: '<thoughts>')
|
|
73
|
+
* @returns ReasoningBlock or null if not found
|
|
74
|
+
*/
|
|
75
|
+
export function parseSingleReasoning(content, startTag = '<thoughts>') {
|
|
76
|
+
if (!content || typeof content !== 'string') {
|
|
77
|
+
return null;
|
|
78
|
+
}
|
|
79
|
+
try {
|
|
80
|
+
// Extract tag name from startTag (e.g., 'thoughts' from '<thoughts>')
|
|
81
|
+
const tagName = startTag.replace(/^<\s*/, '').replace(/\s*>$/, '');
|
|
82
|
+
// Create flexible pattern that handles whitespace variations
|
|
83
|
+
const pattern = new RegExp(`<\\s*${tagName}\\s*>([\\s\\S]*?)<\\s*/${tagName}\\s*>`, 'gi');
|
|
84
|
+
const match = pattern.exec(content);
|
|
85
|
+
if (!match) {
|
|
86
|
+
return null;
|
|
87
|
+
}
|
|
88
|
+
const fullMatch = match[0];
|
|
89
|
+
const contentText = match[1] ?? '';
|
|
90
|
+
const firstBracket = fullMatch.indexOf('>');
|
|
91
|
+
const lastBracket = fullMatch.lastIndexOf('<');
|
|
92
|
+
return {
|
|
93
|
+
content: contentText.trim(),
|
|
94
|
+
startTag: fullMatch.substring(0, firstBracket + 1),
|
|
95
|
+
endTag: fullMatch.substring(lastBracket),
|
|
96
|
+
startPosition: match.index,
|
|
97
|
+
endPosition: match.index + fullMatch.length
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
catch (error) {
|
|
101
|
+
console.error('[Parser] Error parsing single block:', error);
|
|
102
|
+
return null;
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
/**
|
|
106
|
+
* Quick check if content contains any reasoning blocks
|
|
107
|
+
* @param content - Raw content
|
|
108
|
+
* @returns boolean indicating if any reasoning blocks are present
|
|
109
|
+
*/
|
|
110
|
+
export function hasReasoning(content) {
|
|
111
|
+
if (!content || typeof content !== 'string') {
|
|
112
|
+
return false;
|
|
113
|
+
}
|
|
114
|
+
try {
|
|
115
|
+
return DEFAULT_PATTERNS.some(pattern => {
|
|
116
|
+
pattern.pattern.lastIndex = 0;
|
|
117
|
+
return pattern.pattern.test(content);
|
|
118
|
+
});
|
|
119
|
+
}
|
|
120
|
+
catch (error) {
|
|
121
|
+
console.error('[Parser] Error checking for reasoning:', error);
|
|
122
|
+
return false;
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
/**
|
|
126
|
+
* Extract reasoning content only (without tags)
|
|
127
|
+
* @param content - Raw content
|
|
128
|
+
* @returns Concatenated reasoning content from all blocks
|
|
129
|
+
*/
|
|
130
|
+
export function extractReasoningContent(content) {
|
|
131
|
+
const blocks = parseReasoning(content);
|
|
132
|
+
if (blocks.length === 0) {
|
|
133
|
+
return '';
|
|
134
|
+
}
|
|
135
|
+
return blocks.map(block => block.content).join('\n\n');
|
|
136
|
+
}
|
|
137
|
+
/**
|
|
138
|
+
* Remove all reasoning blocks from content
|
|
139
|
+
* @param content - Raw content
|
|
140
|
+
* @returns Content with reasoning blocks removed
|
|
141
|
+
*/
|
|
142
|
+
export function removeReasoningBlocks(content) {
|
|
143
|
+
if (!content || typeof content !== 'string') {
|
|
144
|
+
return content;
|
|
145
|
+
}
|
|
146
|
+
let result = content;
|
|
147
|
+
try {
|
|
148
|
+
for (const patternConfig of DEFAULT_PATTERNS) {
|
|
149
|
+
const pattern = patternConfig.pattern;
|
|
150
|
+
pattern.lastIndex = 0;
|
|
151
|
+
result = result.replace(pattern, '');
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
catch (error) {
|
|
155
|
+
console.error('[Parser] Error removing reasoning blocks:', error);
|
|
156
|
+
}
|
|
157
|
+
return result;
|
|
158
|
+
}
|
|
159
|
+
//# sourceMappingURL=parser.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"parser.js","sourceRoot":"","sources":["../src/parser.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Transformer for Reasoning Blocks
|
|
3
|
+
*
|
|
4
|
+
* Converts raw reasoning blocks into formatted, readable output.
|
|
5
|
+
* Cleans up whitespace, adds markdown formatting, and reconstructs
|
|
6
|
+
* the full content with reasoning visible.
|
|
7
|
+
*/
|
|
8
|
+
import type { ReasoningBlock, TransformResult } from './types.js';
|
|
9
|
+
/**
|
|
10
|
+
* Format a single reasoning block for display
|
|
11
|
+
* @param reasoning - Raw reasoning content
|
|
12
|
+
* @returns Formatted reasoning string with header
|
|
13
|
+
*/
|
|
14
|
+
export declare function formatReasoning(reasoning: string): string;
|
|
15
|
+
/**
|
|
16
|
+
* Transform content with reasoning blocks into formatted output
|
|
17
|
+
* @param content - Original content with reasoning tags
|
|
18
|
+
* @param blocks - Array of parsed ReasoningBlock objects
|
|
19
|
+
* @returns TransformResult with transformed content
|
|
20
|
+
*/
|
|
21
|
+
export declare function transformContent(content: string, blocks: ReasoningBlock[]): TransformResult;
|
|
22
|
+
/**
|
|
23
|
+
* Transform content using regex patterns (alternative approach)
|
|
24
|
+
* @param content - Original content
|
|
25
|
+
* @returns TransformResult
|
|
26
|
+
*/
|
|
27
|
+
export declare function transformWithRegex(content: string): TransformResult;
|
|
28
|
+
/**
|
|
29
|
+
* Check if transformation is needed (has reasoning blocks)
|
|
30
|
+
* @param content - Content to check
|
|
31
|
+
* @returns boolean
|
|
32
|
+
*/
|
|
33
|
+
export declare function needsTransformation(content: string): boolean;
|
|
34
|
+
//# sourceMappingURL=transformer.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"transformer.d.ts","sourceRoot":"","sources":["../src/transformer.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,KAAK,EAAE,cAAc,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC;AAGlE;;;;GAIG;AACH,wBAAgB,eAAe,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,CAazD;AAED;;;;;GAKG;AACH,wBAAgB,gBAAgB,CAC9B,OAAO,EAAE,MAAM,EACf,MAAM,EAAE,cAAc,EAAE,GACvB,eAAe,CAmCjB;AAED;;;;GAIG;AACH,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,MAAM,GAAG,eAAe,CAoFnE;AAwBD;;;;GAIG;AACH,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAa5D"}
|
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Transformer for Reasoning Blocks
|
|
3
|
+
*
|
|
4
|
+
* Converts raw reasoning blocks into formatted, readable output.
|
|
5
|
+
* Cleans up whitespace, adds markdown formatting, and reconstructs
|
|
6
|
+
* the full content with reasoning visible.
|
|
7
|
+
*/
|
|
8
|
+
import { DEFAULT_PATTERNS } from './types.js';
|
|
9
|
+
/**
|
|
10
|
+
* Format a single reasoning block for display
|
|
11
|
+
* @param reasoning - Raw reasoning content
|
|
12
|
+
* @returns Formatted reasoning string with header
|
|
13
|
+
*/
|
|
14
|
+
export function formatReasoning(reasoning) {
|
|
15
|
+
if (!reasoning) {
|
|
16
|
+
return '**Reasoning Process:**\n';
|
|
17
|
+
}
|
|
18
|
+
// Clean up internal formatting
|
|
19
|
+
const cleaned = reasoning
|
|
20
|
+
.replace(/^\s+|\s+$/g, '') // Trim leading/trailing whitespace
|
|
21
|
+
.replace(/\n{3,}/g, '\n\n') // Collapse multiple newlines to double
|
|
22
|
+
.replace(/\n\s*\n/g, '\n\n') // Normalize whitespace between lines
|
|
23
|
+
.trim();
|
|
24
|
+
return `**Reasoning Process:**\n${cleaned}`;
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Transform content with reasoning blocks into formatted output
|
|
28
|
+
* @param content - Original content with reasoning tags
|
|
29
|
+
* @param blocks - Array of parsed ReasoningBlock objects
|
|
30
|
+
* @returns TransformResult with transformed content
|
|
31
|
+
*/
|
|
32
|
+
export function transformContent(content, blocks) {
|
|
33
|
+
if (!blocks || blocks.length === 0) {
|
|
34
|
+
return {
|
|
35
|
+
original: content,
|
|
36
|
+
transformed: content,
|
|
37
|
+
blocksFound: 0,
|
|
38
|
+
blocks: []
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
// Extract and format all reasoning blocks
|
|
42
|
+
const reasoningBlocks = blocks.map(block => formatReasoning(block.content));
|
|
43
|
+
// Remove tags from original content using position-based splicing
|
|
44
|
+
let cleanContent = content;
|
|
45
|
+
const sortedBlocks = [...blocks].sort((a, b) => b.startPosition - a.startPosition);
|
|
46
|
+
for (const block of sortedBlocks) {
|
|
47
|
+
cleanContent = cleanContent.substring(0, block.startPosition) +
|
|
48
|
+
cleanContent.substring(block.endPosition);
|
|
49
|
+
}
|
|
50
|
+
// Reconstruct with reasoning visible
|
|
51
|
+
const transformed = [
|
|
52
|
+
...reasoningBlocks,
|
|
53
|
+
'---',
|
|
54
|
+
cleanContent.trim()
|
|
55
|
+
].join('\n\n');
|
|
56
|
+
return {
|
|
57
|
+
original: content,
|
|
58
|
+
transformed,
|
|
59
|
+
blocksFound: blocks.length,
|
|
60
|
+
blocks
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Transform content using regex patterns (alternative approach)
|
|
65
|
+
* @param content - Original content
|
|
66
|
+
* @returns TransformResult
|
|
67
|
+
*/
|
|
68
|
+
export function transformWithRegex(content) {
|
|
69
|
+
if (!content || typeof content !== 'string') {
|
|
70
|
+
return {
|
|
71
|
+
original: '',
|
|
72
|
+
transformed: '',
|
|
73
|
+
blocksFound: 0,
|
|
74
|
+
blocks: []
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
const allBlocks = [];
|
|
78
|
+
try {
|
|
79
|
+
// Collect all matches from all patterns
|
|
80
|
+
for (const patternConfig of DEFAULT_PATTERNS) {
|
|
81
|
+
const pattern = patternConfig.pattern;
|
|
82
|
+
pattern.lastIndex = 0;
|
|
83
|
+
const matches = [...content.matchAll(pattern)];
|
|
84
|
+
for (const match of matches) {
|
|
85
|
+
const fullMatch = match[0];
|
|
86
|
+
const blockContent = match[1] ?? '';
|
|
87
|
+
const firstBracket = fullMatch.indexOf('>');
|
|
88
|
+
const lastBracket = fullMatch.lastIndexOf('<');
|
|
89
|
+
allBlocks.push({
|
|
90
|
+
content: blockContent.trim(),
|
|
91
|
+
startTag: fullMatch.substring(0, firstBracket + 1),
|
|
92
|
+
endTag: fullMatch.substring(lastBracket),
|
|
93
|
+
startPosition: match.index,
|
|
94
|
+
endPosition: match.index + fullMatch.length
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
catch (error) {
|
|
100
|
+
console.error('[Transformer] Error in transformWithRegex:', error);
|
|
101
|
+
return {
|
|
102
|
+
original: content,
|
|
103
|
+
transformed: content,
|
|
104
|
+
blocksFound: 0,
|
|
105
|
+
blocks: []
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
// Remove duplicates
|
|
109
|
+
const uniqueBlocks = removeDuplicateBlocks(allBlocks);
|
|
110
|
+
// Sort by position
|
|
111
|
+
const sortedBlocks = uniqueBlocks.sort((a, b) => a.startPosition - b.startPosition);
|
|
112
|
+
if (sortedBlocks.length === 0) {
|
|
113
|
+
return {
|
|
114
|
+
original: content,
|
|
115
|
+
transformed: content,
|
|
116
|
+
blocksFound: 0,
|
|
117
|
+
blocks: []
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
// Format reasoning blocks
|
|
121
|
+
const reasoningBlocks = sortedBlocks.map(block => formatReasoning(block.content));
|
|
122
|
+
// Remove reasoning blocks from content (work from end to start)
|
|
123
|
+
let cleanContent = content;
|
|
124
|
+
for (const block of sortedBlocks.reverse()) {
|
|
125
|
+
cleanContent = cleanContent.substring(0, block.startPosition) +
|
|
126
|
+
cleanContent.substring(block.endPosition);
|
|
127
|
+
}
|
|
128
|
+
// Reconstruct with reasoning visible
|
|
129
|
+
const transformed = [
|
|
130
|
+
...reasoningBlocks,
|
|
131
|
+
'---',
|
|
132
|
+
cleanContent.trim()
|
|
133
|
+
].join('\n\n');
|
|
134
|
+
return {
|
|
135
|
+
original: content,
|
|
136
|
+
transformed,
|
|
137
|
+
blocksFound: sortedBlocks.length,
|
|
138
|
+
blocks: sortedBlocks
|
|
139
|
+
};
|
|
140
|
+
}
|
|
141
|
+
/**
|
|
142
|
+
* Remove duplicate blocks that may be detected by multiple patterns
|
|
143
|
+
*/
|
|
144
|
+
function removeDuplicateBlocks(blocks) {
|
|
145
|
+
if (blocks.length <= 1) {
|
|
146
|
+
return blocks;
|
|
147
|
+
}
|
|
148
|
+
const seen = new Set();
|
|
149
|
+
const result = [];
|
|
150
|
+
for (const block of blocks) {
|
|
151
|
+
const key = `${block.startPosition}-${block.endPosition}`;
|
|
152
|
+
if (!seen.has(key)) {
|
|
153
|
+
seen.add(key);
|
|
154
|
+
result.push(block);
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
return result;
|
|
158
|
+
}
|
|
159
|
+
/**
|
|
160
|
+
* Check if transformation is needed (has reasoning blocks)
|
|
161
|
+
* @param content - Content to check
|
|
162
|
+
* @returns boolean
|
|
163
|
+
*/
|
|
164
|
+
export function needsTransformation(content) {
|
|
165
|
+
if (!content || typeof content !== 'string') {
|
|
166
|
+
return false;
|
|
167
|
+
}
|
|
168
|
+
try {
|
|
169
|
+
return DEFAULT_PATTERNS.some(pattern => {
|
|
170
|
+
pattern.pattern.lastIndex = 0;
|
|
171
|
+
return pattern.pattern.test(content);
|
|
172
|
+
});
|
|
173
|
+
}
|
|
174
|
+
catch {
|
|
175
|
+
return false;
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
//# sourceMappingURL=transformer.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"transformer.js","sourceRoot":"","sources":["../src/transformer.ts"],"names":[],"mappings":"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"}
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Think-Inc Type Definitions
|
|
3
|
+
*
|
|
4
|
+
* Comprehensive TypeScript interfaces for the Think-Inc plugin.
|
|
5
|
+
* All types are strictly typed with no `any` types, supporting
|
|
6
|
+
* future extensibility for custom tag patterns.
|
|
7
|
+
*/
|
|
8
|
+
/**
|
|
9
|
+
* Represents a single reasoning block extracted from content.
|
|
10
|
+
* Contains both the extracted content and position information
|
|
11
|
+
* for potential debugging or transformation purposes.
|
|
12
|
+
*/
|
|
13
|
+
export interface ReasoningBlock {
|
|
14
|
+
content: string;
|
|
15
|
+
startTag: string;
|
|
16
|
+
endTag: string;
|
|
17
|
+
startPosition: number;
|
|
18
|
+
endPosition: number;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Result of transforming content with reasoning blocks.
|
|
22
|
+
* Tracks the original content, transformed output, and all
|
|
23
|
+
* extracted reasoning blocks for debugging and analysis.
|
|
24
|
+
*/
|
|
25
|
+
export interface TransformResult {
|
|
26
|
+
original: string;
|
|
27
|
+
transformed: string;
|
|
28
|
+
blocksFound: number;
|
|
29
|
+
blocks: ReasoningBlock[];
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Configuration options for the Think-Inc plugin.
|
|
33
|
+
* Allows enabling/disabling the plugin and customizing
|
|
34
|
+
* tag patterns and formatting behavior.
|
|
35
|
+
*/
|
|
36
|
+
export interface ThinkIncConfig {
|
|
37
|
+
enabled: boolean;
|
|
38
|
+
tagPatterns: string[];
|
|
39
|
+
formatTemplate?: string;
|
|
40
|
+
maxReasoningLength?: number;
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Represents a regex pattern for detecting reasoning blocks.
|
|
44
|
+
* Supports multiple tag formats and provides descriptive metadata
|
|
45
|
+
* for debugging and configuration purposes.
|
|
46
|
+
*/
|
|
47
|
+
export interface RegexPattern {
|
|
48
|
+
name: string;
|
|
49
|
+
pattern: RegExp;
|
|
50
|
+
description: string;
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Default regex patterns for detecting various reasoning tag formats.
|
|
54
|
+
* Includes support for standard thoughts tags, generic think tags,
|
|
55
|
+
* reasoning tags, and Qwen model-specific format.
|
|
56
|
+
*/
|
|
57
|
+
export declare const DEFAULT_PATTERNS: RegexPattern[];
|
|
58
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH;;;;GAIG;AACH,MAAM,WAAW,cAAc;IAC7B,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,MAAM,CAAC;IACf,aAAa,EAAE,MAAM,CAAC;IACtB,WAAW,EAAE,MAAM,CAAC;CACrB;AAED;;;;GAIG;AACH,MAAM,WAAW,eAAe;IAC9B,QAAQ,EAAE,MAAM,CAAC;IACjB,WAAW,EAAE,MAAM,CAAC;IACpB,WAAW,EAAE,MAAM,CAAC;IACpB,MAAM,EAAE,cAAc,EAAE,CAAC;CAC1B;AAED;;;;GAIG;AACH,MAAM,WAAW,cAAc;IAC7B,OAAO,EAAE,OAAO,CAAC;IACjB,WAAW,EAAE,MAAM,EAAE,CAAC;IACtB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,kBAAkB,CAAC,EAAE,MAAM,CAAC;CAC7B;AAED;;;;GAIG;AACH,MAAM,WAAW,YAAY;IAC3B,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,WAAW,EAAE,MAAM,CAAC;CACrB;AAED;;;;GAIG;AACH,eAAO,MAAM,gBAAgB,EAAE,YAAY,EAsB1C,CAAC"}
|
package/dist/types.js
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Think-Inc Type Definitions
|
|
3
|
+
*
|
|
4
|
+
* Comprehensive TypeScript interfaces for the Think-Inc plugin.
|
|
5
|
+
* All types are strictly typed with no `any` types, supporting
|
|
6
|
+
* future extensibility for custom tag patterns.
|
|
7
|
+
*/
|
|
8
|
+
/**
|
|
9
|
+
* Default regex patterns for detecting various reasoning tag formats.
|
|
10
|
+
* Includes support for standard thoughts tags, generic think tags,
|
|
11
|
+
* reasoning tags, and Qwen model-specific format.
|
|
12
|
+
*/
|
|
13
|
+
export const DEFAULT_PATTERNS = [
|
|
14
|
+
{
|
|
15
|
+
name: 'thoughts',
|
|
16
|
+
pattern: /<\s*thoughts\s*>([\s\S]*?)<\s*\/\s*thoughts\s*>/gi,
|
|
17
|
+
description: 'Standard thoughts tags'
|
|
18
|
+
},
|
|
19
|
+
{
|
|
20
|
+
name: 'think',
|
|
21
|
+
pattern: /<\s*think\s*>([\s\S]*?)<\s*\/\s*think\s*>/gi,
|
|
22
|
+
description: 'Generic think tags'
|
|
23
|
+
},
|
|
24
|
+
{
|
|
25
|
+
name: 'reasoning',
|
|
26
|
+
pattern: /<\s*reasoning\s*>([\s\S]*?)<\s*\/\s*reasoning\s*>/gi,
|
|
27
|
+
description: 'Reasoning tags'
|
|
28
|
+
},
|
|
29
|
+
{
|
|
30
|
+
name: 'qwen',
|
|
31
|
+
// Qwen model format - uses special token sequences
|
|
32
|
+
pattern: /<\/?thoughts>[\s\S]*?<\/thoughts>/gi,
|
|
33
|
+
description: 'Qwen model format'
|
|
34
|
+
}
|
|
35
|
+
];
|
|
36
|
+
//# sourceMappingURL=types.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAkDH;;;;GAIG;AACH,MAAM,CAAC,MAAM,gBAAgB,GAAmB;IAC9C;QACE,IAAI,EAAE,UAAU;QAChB,OAAO,EAAE,mDAAmD;QAC5D,WAAW,EAAE,wBAAwB;KACtC;IACD;QACE,IAAI,EAAE,OAAO;QACb,OAAO,EAAE,6CAA6C;QACtD,WAAW,EAAE,oBAAoB;KAClC;IACD;QACE,IAAI,EAAE,WAAW;QACjB,OAAO,EAAE,qDAAqD;QAC9D,WAAW,EAAE,gBAAgB;KAC9B;IACD;QACE,IAAI,EAAE,MAAM;QACZ,mDAAmD;QACnD,OAAO,EAAE,qCAAqC;QAC9C,WAAW,EAAE,mBAAmB;KACjC;CACF,CAAC"}
|
package/package.json
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "think-inc",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "OpenCode plugin that displays LLM reasoning traces in conversations",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "dist/index.js",
|
|
7
|
+
"types": "dist/index.d.ts",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": "./dist/index.js"
|
|
10
|
+
},
|
|
11
|
+
"keywords": [
|
|
12
|
+
"opencode",
|
|
13
|
+
"plugin",
|
|
14
|
+
"reasoning",
|
|
15
|
+
"llm",
|
|
16
|
+
"think-inc"
|
|
17
|
+
],
|
|
18
|
+
"repository": {
|
|
19
|
+
"type": "git",
|
|
20
|
+
"url": "https://github.com/whpthomas/think-inc.git"
|
|
21
|
+
},
|
|
22
|
+
"author": "whpthomas",
|
|
23
|
+
"license": "MIT",
|
|
24
|
+
"bugs": {
|
|
25
|
+
"url": "https://github.com/whpthomas/think-inc/issues"
|
|
26
|
+
},
|
|
27
|
+
"homepage": "https://github.com/whpthomas/think-inc#readme",
|
|
28
|
+
"scripts": {
|
|
29
|
+
"build": "bun build src/index.ts --outdir dist --target node --minify",
|
|
30
|
+
"dev": "bun build src/index.ts --outdir dist --target node --watch",
|
|
31
|
+
"clean": "rm -rf dist",
|
|
32
|
+
"test": "bun test",
|
|
33
|
+
"prepublishOnly": "bun run build"
|
|
34
|
+
},
|
|
35
|
+
"peerDependencies": {
|
|
36
|
+
"@opencode-ai/plugin": "^1.4.0",
|
|
37
|
+
"@opencode-ai/sdk": "^1.4.0"
|
|
38
|
+
},
|
|
39
|
+
"devDependencies": {
|
|
40
|
+
"@types/node": "^25.6.0",
|
|
41
|
+
"bun-types": "^1.1.0",
|
|
42
|
+
"typescript": "^5.4.5"
|
|
43
|
+
}
|
|
44
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Think-Inc Plugin - Reasoning Block Interceptor
|
|
3
|
+
*
|
|
4
|
+
* Captures reasoning parts as they stream in, stores them temporarily,
|
|
5
|
+
* then injects them back into message context for the next turn.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import type { Plugin, Hooks, PluginInput } from '@opencode-ai/plugin';
|
|
9
|
+
import { log, clearLog, flush } from './logger.js';
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Store reasoning per session with completion tracking
|
|
13
|
+
*
|
|
14
|
+
* Reasoning arrives via streaming events, but needs to persist until
|
|
15
|
+
* the transform hook runs to inject it into message context.
|
|
16
|
+
*/
|
|
17
|
+
interface ReasoningStore {
|
|
18
|
+
reasoning: Map<string, string[]>; // messageID -> reasoning chunks (accumulating)
|
|
19
|
+
complete: Set<string>; // messageIDs that have finished streaming
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
// Global store: sessionID -> ReasoningStore
|
|
23
|
+
// Needed because reasoning chunks arrive asynchronously and must persist
|
|
24
|
+
// until the transform hook runs to inject them into context.
|
|
25
|
+
const sessionStores = new Map<string, ReasoningStore>();
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Main plugin implementation
|
|
29
|
+
*/
|
|
30
|
+
export const thoughtsIncPlugin: Plugin = async (_input: PluginInput): Promise<Hooks> => {
|
|
31
|
+
clearLog();
|
|
32
|
+
log('=== Think-Inc Plugin Initialized ===');
|
|
33
|
+
|
|
34
|
+
const hooks: Hooks = {
|
|
35
|
+
/**
|
|
36
|
+
* Event Hook: Capture reasoning chunks as they stream in
|
|
37
|
+
*
|
|
38
|
+
* Triggered on every event from the model. We listen for 'message.part.updated'
|
|
39
|
+
* events where the part type is 'reasoning'. Each chunk is accumulated in
|
|
40
|
+
* sessionStores so it can be injected later when the message context is built.
|
|
41
|
+
*/
|
|
42
|
+
event: async ({ event }) => {
|
|
43
|
+
if (event.type !== 'message.part.updated') return;
|
|
44
|
+
|
|
45
|
+
const properties = event.properties as any;
|
|
46
|
+
const part = properties.part;
|
|
47
|
+
|
|
48
|
+
// Only capture reasoning parts
|
|
49
|
+
if (part.type !== 'reasoning') return;
|
|
50
|
+
if (!part.text?.trim()) return;
|
|
51
|
+
|
|
52
|
+
const messageID = part.messageID;
|
|
53
|
+
const sessionID = part.sessionID;
|
|
54
|
+
const preview = part.text.slice(0, 35).replace(/\n/g, '\\n');
|
|
55
|
+
log(`[CAPTURE] "${preview}..." ${part.text.length} chars`);
|
|
56
|
+
|
|
57
|
+
// Initialize or get session store
|
|
58
|
+
if (!sessionStores.has(sessionID)) {
|
|
59
|
+
sessionStores.set(sessionID, {
|
|
60
|
+
reasoning: new Map(),
|
|
61
|
+
complete: new Set()
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
const store = sessionStores.get(sessionID)!;
|
|
65
|
+
|
|
66
|
+
// Initialize message array if needed
|
|
67
|
+
if (!store.reasoning.has(messageID)) {
|
|
68
|
+
store.reasoning.set(messageID, []);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// Add reasoning chunk to accumulate all pieces
|
|
72
|
+
store.reasoning.get(messageID)!.push(part.text);
|
|
73
|
+
},
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Transform Hook: Inject captured reasoning into message context
|
|
77
|
+
*
|
|
78
|
+
* This runs when building the message context for the next turn. We:
|
|
79
|
+
* 1. Look up any captured reasoning for each message from sessionStores
|
|
80
|
+
* 2. Combine all chunks into full reasoning text
|
|
81
|
+
* 3. Inject it as <reasoning> tags at the top of the first text part
|
|
82
|
+
* 4. Mark as complete and clean up to prevent re-injection
|
|
83
|
+
*
|
|
84
|
+
* Iterates backwards from the most recent message since that's where
|
|
85
|
+
* new reasoning would be. Stops after injection to avoid unnecessary processing.
|
|
86
|
+
*/
|
|
87
|
+
'experimental.chat.messages.transform': async (_input, output) => {
|
|
88
|
+
log(`[TRANSFORM] Processing ${output.messages.length} messages`);
|
|
89
|
+
|
|
90
|
+
// Iterate backwards from most recent message
|
|
91
|
+
for (let i = output.messages.length - 1; i >= 0; i--) {
|
|
92
|
+
const msg = output.messages[i];
|
|
93
|
+
const messageID = msg.info.id;
|
|
94
|
+
const sessionID = msg.info.sessionID;
|
|
95
|
+
|
|
96
|
+
const store = sessionStores.get(sessionID);
|
|
97
|
+
if (!store) {
|
|
98
|
+
continue;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
if (!store.reasoning.has(messageID)) {
|
|
102
|
+
continue;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
if (store.complete.has(messageID)) {
|
|
106
|
+
log(`[TRANSFORM] Message ${messageID} already completed`);
|
|
107
|
+
break;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// Combine all streaming chunks into complete reasoning text
|
|
111
|
+
const combinedReasoning = store.reasoning.get(messageID)!
|
|
112
|
+
.filter(r => r.trim())
|
|
113
|
+
.join('\n');
|
|
114
|
+
|
|
115
|
+
if (combinedReasoning.trim()) {
|
|
116
|
+
// Inject reasoning as a new text part at the beginning of the message
|
|
117
|
+
// This works even for tool-only messages that have no existing text parts
|
|
118
|
+
const reasoningPart: any = {
|
|
119
|
+
type: 'text',
|
|
120
|
+
text: `<reasoning>\n${combinedReasoning}\n</reasoning>`,
|
|
121
|
+
synthetic: true,
|
|
122
|
+
id: `reasoning-${messageID}`,
|
|
123
|
+
sessionID: sessionID,
|
|
124
|
+
messageID: messageID,
|
|
125
|
+
};
|
|
126
|
+
// Insert reasoning part at the beginning of the message
|
|
127
|
+
msg.parts.unshift(reasoningPart);
|
|
128
|
+
|
|
129
|
+
log(`[INJECT] ${combinedReasoning.length} chars ${messageID} ${sessionID}`);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
store.complete.add(messageID);
|
|
133
|
+
store.reasoning.delete(messageID);
|
|
134
|
+
|
|
135
|
+
// Cleanup: remove sessions with no pending reasoning
|
|
136
|
+
for (const [sessionID, store] of sessionStores) {
|
|
137
|
+
if (store.reasoning.size === 0 && store.complete.size > 0) {
|
|
138
|
+
sessionStores.delete(sessionID);
|
|
139
|
+
log(`[CLEANUP] Session ${sessionID} cleaned up`);
|
|
140
|
+
await flush();
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
// Stop after first injection - newer messages processed first
|
|
145
|
+
break;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
return output;
|
|
149
|
+
},
|
|
150
|
+
};
|
|
151
|
+
|
|
152
|
+
return hooks;
|
|
153
|
+
};
|
|
154
|
+
|
|
155
|
+
export default thoughtsIncPlugin;
|
package/src/logger.ts
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import * as fs from "fs";
|
|
2
|
+
import * as path from "path";
|
|
3
|
+
|
|
4
|
+
const LOG_DIR = path.join(process.cwd(), ".logs");
|
|
5
|
+
const LOG_FILE = path.join(LOG_DIR, "think-inc.log");
|
|
6
|
+
|
|
7
|
+
let writeStream: fs.WriteStream | null = null;
|
|
8
|
+
let initialized = false;
|
|
9
|
+
|
|
10
|
+
function ensureInitialized(): boolean {
|
|
11
|
+
if (initialized) return true;
|
|
12
|
+
|
|
13
|
+
try {
|
|
14
|
+
if (!fs.existsSync(LOG_DIR)) {
|
|
15
|
+
fs.mkdirSync(LOG_DIR, { recursive: true });
|
|
16
|
+
}
|
|
17
|
+
writeStream = fs.createWriteStream(LOG_FILE, { flags: "a" });
|
|
18
|
+
initialized = true;
|
|
19
|
+
return true;
|
|
20
|
+
} catch {
|
|
21
|
+
return false;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function formatTimestamp(): string {
|
|
26
|
+
return new Date().toISOString();
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export function log(...args: unknown[]) {
|
|
30
|
+
if (!ensureInitialized() || !writeStream) return;
|
|
31
|
+
|
|
32
|
+
const timestamp = formatTimestamp();
|
|
33
|
+
const message = args
|
|
34
|
+
.map(a => (typeof a === "object" ? JSON.stringify(a, null, 2) : String(a)))
|
|
35
|
+
.join(" ");
|
|
36
|
+
writeStream.write(`[${timestamp}] ${message}\n`);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export function flush(): Promise<void> {
|
|
40
|
+
if (!writeStream) return Promise.resolve();
|
|
41
|
+
|
|
42
|
+
return new Promise((resolve) => {
|
|
43
|
+
writeStream!.end(() => {
|
|
44
|
+
writeStream = null;
|
|
45
|
+
initialized = false;
|
|
46
|
+
resolve();
|
|
47
|
+
});
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export function clearLog() {
|
|
52
|
+
if (!ensureInitialized()) return;
|
|
53
|
+
writeStream?.close();
|
|
54
|
+
fs.writeFileSync(LOG_FILE, "");
|
|
55
|
+
writeStream = fs.createWriteStream(LOG_FILE, { flags: "a" });
|
|
56
|
+
}
|
package/tsconfig.json
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "ES2022",
|
|
4
|
+
"module": "ESNext",
|
|
5
|
+
"moduleResolution": "bundler",
|
|
6
|
+
"lib": ["ES2022"],
|
|
7
|
+
"outDir": "./dist",
|
|
8
|
+
"rootDir": "./src",
|
|
9
|
+
"declaration": true,
|
|
10
|
+
"declarationMap": true,
|
|
11
|
+
"sourceMap": true,
|
|
12
|
+
"strict": true,
|
|
13
|
+
"noImplicitAny": true,
|
|
14
|
+
"strictNullChecks": true,
|
|
15
|
+
"strictFunctionTypes": true,
|
|
16
|
+
"noUnusedLocals": true,
|
|
17
|
+
"noUnusedParameters": true,
|
|
18
|
+
"noImplicitReturns": true,
|
|
19
|
+
"noFallthroughCasesInSwitch": true,
|
|
20
|
+
"esModuleInterop": true,
|
|
21
|
+
"skipLibCheck": true,
|
|
22
|
+
"forceConsistentCasingInFileNames": true
|
|
23
|
+
},
|
|
24
|
+
"include": ["src/**/*"],
|
|
25
|
+
"exclude": ["node_modules", "dist", ".opencode"]
|
|
26
|
+
}
|