mirror of
https://github.com/tiennm99/ccs.git
synced 2026-10-05 10:13:12 +00:00
fix(glmt): emit thinking signature as opaque string
generateThinkingSignature() returned an object
({type, hash, length, timestamp}) but Anthropic requires a thinking
block's `signature` to be a non-empty opaque STRING. The object made
the thinking block invalid, breaking the stream against reasoning
backends and surfacing as a false 502 "did not respond within 600s".
Return a deterministic base64 token instead (no Date.now()), and
narrow ThinkingSignature to a string alias so existing imports stay
intact. The signature is only ever assigned, never read as an object,
so consumers are unaffected. Update the existing unit test to the
corrected string contract.
Built [OnSteroids](https://onsteroids.ai)
This commit is contained in:
1 parent
37d2bae001
commit
cbcdb6ba62
3 files changed
+17
-20
No files matched your search
@@ -177,17 +177,16 @@ export class ResponseBuilder {
|
||||
|
||||
/**
|
||||
* Generate thinking signature for Claude Code UI
|
||||
*
|
||||
* Anthropic requires a thinking block's `signature` to be a non-empty
|
||||
* opaque STRING. Emit a deterministic base64 token (no Date.now()) so the
|
||||
* thinking block stays valid against reasoning backends; returning an object
|
||||
* here produces an invalid thinking block and breaks the stream.
|
||||
*/
|
||||
generateThinkingSignature(thinking: string): ThinkingSignature {
|
||||
// Generate signature hash
|
||||
const hash = crypto.createHash('sha256').update(thinking).digest('hex').substring(0, 16);
|
||||
|
||||
return {
|
||||
type: 'thinking_signature',
|
||||
hash: hash,
|
||||
length: thinking.length,
|
||||
timestamp: Date.now(),
|
||||
};
|
||||
return Buffer.from(`ccs-thinking:${hash}:${thinking.length}`).toString('base64');
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -7,7 +7,7 @@ export interface ContentBlock {
|
||||
type: string;
|
||||
text?: string;
|
||||
thinking?: string;
|
||||
signature?: ThinkingSignature;
|
||||
signature?: string;
|
||||
id?: string;
|
||||
name?: string;
|
||||
input?: Record<string, unknown>;
|
||||
@@ -95,12 +95,9 @@ export interface TransformResult {
|
||||
error?: string;
|
||||
}
|
||||
|
||||
export interface ThinkingSignature {
|
||||
type: string;
|
||||
hash: string;
|
||||
length: number;
|
||||
timestamp: number;
|
||||
}
|
||||
// Anthropic contract: a thinking block's signature is a non-empty opaque
|
||||
// string. Kept as a named alias so existing imports stay intact.
|
||||
export type ThinkingSignature = string;
|
||||
|
||||
// OpenAI response types
|
||||
export interface OpenAIChoice {
|
||||
|
||||
@@ -161,16 +161,17 @@ describe('GlmtTransformer', () => {
|
||||
});
|
||||
|
||||
describe('Thinking signature', () => {
|
||||
it('generates thinking signature', () => {
|
||||
it('generates thinking signature as a non-empty opaque string', () => {
|
||||
const transformer = new GlmtTransformer();
|
||||
const thinking = 'This is my reasoning process';
|
||||
const signature = transformer.generateThinkingSignature(thinking);
|
||||
|
||||
assert.strictEqual(signature.type, 'thinking_signature');
|
||||
assert.ok(signature.hash);
|
||||
assert.strictEqual(signature.hash.length, 16);
|
||||
assert.strictEqual(signature.length, thinking.length);
|
||||
assert.ok(signature.timestamp);
|
||||
// Anthropic contract: a thinking block's signature is a non-empty
|
||||
// opaque string, not an object.
|
||||
assert.strictEqual(typeof signature, 'string');
|
||||
assert.ok(signature.length > 0);
|
||||
// Deterministic: same reasoning yields the same signature (no Date.now()).
|
||||
assert.strictEqual(signature, transformer.generateThinkingSignature(thinking));
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
Reference in new issue
Block a user