Skip to content

Commit bced728

Browse files
authored
feat: add output_text property on Response object (#42)
Mirroring the python SDK. Used by integration tests.
1 parent 5d575f0 commit bced728

File tree

2 files changed

+129
-0
lines changed

2 files changed

+129
-0
lines changed

src/index.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -151,6 +151,9 @@ import {
151151
VectorStoresOpenAICursorPage,
152152
} from './resources/vector-stores/vector-stores';
153153

154+
// MANUAL: Auto-install response helpers (preserve across regeneration)
155+
import './lib/init';
156+
154157
export interface ClientOptions {
155158
/**
156159
* Defaults to process.env['LLAMA_STACK_CLIENT_API_KEY'].

src/lib/init.ts

Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,126 @@
1+
// Copyright (c) Meta Platforms, Inc. and affiliates.
2+
// All rights reserved.
3+
//
4+
// This source code is licensed under the terms described in the LICENSE file in
5+
// the root directory of this source tree.
6+
7+
/**
8+
* Side-effectful module that installs convenience helpers.
9+
* Imported once from the entry point so the helpers survive regeneration.
10+
*/
11+
12+
import { Responses } from '../resources/responses/responses';
13+
import type { ResponseObject } from '../resources/responses/responses';
14+
import type { APIPromise } from '../core';
15+
16+
// Augment the generated types with the convenience accessor.
17+
declare module '../resources/responses/responses' {
18+
interface ResponseObject {
19+
/**
20+
* Convenience property mirroring the Python client's `output_text`
21+
* aggregation. This concatenates every `output_text` entry under the
22+
* response's `output` list.
23+
*/
24+
readonly output_text: string;
25+
}
26+
}
27+
28+
const OUTPUT_TEXT_PROPERTY = 'output_text';
29+
30+
function collectOutputText(response: ResponseObject): string {
31+
const pieces: string[] = [];
32+
33+
for (const output of response.output ?? []) {
34+
if (!output || output.type !== 'message') {
35+
continue;
36+
}
37+
38+
const content = output.content;
39+
if (typeof content === 'string') {
40+
pieces.push(content);
41+
continue;
42+
}
43+
44+
if (!Array.isArray(content)) {
45+
continue;
46+
}
47+
48+
for (const item of content) {
49+
if (typeof item === 'string') {
50+
pieces.push(item);
51+
continue;
52+
}
53+
if (item && item.type === 'output_text' && 'text' in item && typeof item.text === 'string') {
54+
pieces.push(item.text);
55+
}
56+
}
57+
}
58+
59+
return pieces.join('');
60+
}
61+
62+
function isResponseObject(obj: unknown): obj is ResponseObject {
63+
return (
64+
obj !== null &&
65+
typeof obj === 'object' &&
66+
'object' in obj &&
67+
(obj as { object?: unknown }).object === 'response' &&
68+
'output' in obj &&
69+
Array.isArray((obj as { output?: unknown }).output)
70+
);
71+
}
72+
73+
function addOutputTextGetter(response: ResponseObject): void {
74+
if (OUTPUT_TEXT_PROPERTY in response) {
75+
return;
76+
}
77+
78+
Object.defineProperty(response, OUTPUT_TEXT_PROPERTY, {
79+
get() {
80+
const value = collectOutputText(response);
81+
Object.defineProperty(response, OUTPUT_TEXT_PROPERTY, {
82+
value,
83+
enumerable: true,
84+
configurable: true,
85+
writable: false,
86+
});
87+
return value;
88+
},
89+
enumerable: false,
90+
configurable: true,
91+
});
92+
}
93+
94+
function processResponse<T>(value: T): T {
95+
if (isResponseObject(value)) {
96+
addOutputTextGetter(value);
97+
}
98+
return value;
99+
}
100+
101+
// Patch Responses class methods to automatically add output_text getter
102+
const originalCreate = Responses.prototype.create;
103+
const originalRetrieve = Responses.prototype.retrieve;
104+
105+
// Wrap create method
106+
// @ts-expect-error - Preserving method signature while adding processing
107+
Responses.prototype.create = function (...args: Parameters<typeof originalCreate>) {
108+
const result = originalCreate.apply(this, args);
109+
// Handle both streaming and non-streaming responses
110+
if (result && '_thenUnwrap' in result) {
111+
return result._thenUnwrap((value: any) => {
112+
// Only process if it's not a Stream
113+
if (value && typeof value.on !== 'function' && typeof value[Symbol.asyncIterator] !== 'function') {
114+
return processResponse(value);
115+
}
116+
return value;
117+
}) as typeof result;
118+
}
119+
return result;
120+
};
121+
122+
// Wrap retrieve method
123+
Responses.prototype.retrieve = function (...args: Parameters<typeof originalRetrieve>) {
124+
const result = originalRetrieve.apply(this, args) as APIPromise<any>;
125+
return result._thenUnwrap(processResponse) as typeof result;
126+
};

0 commit comments

Comments
 (0)