Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 32 additions & 2 deletions sdk/js/examples/responses.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,9 @@
// Licensed under the MIT License.
// -------------------------------------------------------------------------

import { FoundryLocalManager, getOutputText } from '../src/index.js';
import type { StreamingEvent, FunctionToolDefinition, FunctionCallItem } from '../src/types.js';
import * as fs from 'fs';
import { FoundryLocalManager, getOutputText, createImageContentFromFile } from '../src/index.js';
import type { StreamingEvent, FunctionToolDefinition, FunctionCallItem, MessageItem } from '../src/types.js';

async function main() {
try {
Expand Down Expand Up @@ -121,6 +122,35 @@ async function main() {
const deleted = await client.delete(stored.id);
console.log(`Deleted: ${deleted.deleted}`);

// =================================================================
// Example 6: List all stored responses
// =================================================================
console.log('\n--- Example 6: List stored responses ---');
const allResponses = await client.list();
console.log(`Listed ${allResponses.data.length} stored responses`);

// =================================================================
// Example 7: Vision — describe an image
// =================================================================
console.log('\n--- Example 7: Vision ---');
const testImagePath = 'path/to/test-image.png'; // Replace with a real image path
Comment thread
MaanavD marked this conversation as resolved.
Outdated
if (fs.existsSync(testImagePath)) {
const imageContent = createImageContentFromFile(testImagePath);
const visionResponse = await client.create([
{
type: 'message',
role: 'user',
content: [
{ type: 'input_text', text: 'Describe this image in one sentence.' },
imageContent,
],
} as MessageItem,
]);
console.log(`Vision: ${getOutputText(visionResponse)}`);
} else {
console.log('(Skipped: test image not found)');
}

// Cleanup
manager.stopWebService();
await model.unload();
Expand Down
1 change: 1 addition & 0 deletions sdk/js/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ export { EmbeddingClient } from './openai/embeddingClient.js';
export { LiveAudioTranscriptionSession, LiveAudioTranscriptionOptions } from './openai/liveAudioTranscriptionClient.js';
export type { LiveAudioTranscriptionResponse, TranscriptionContentPart } from './openai/liveAudioTranscriptionTypes.js';
export { ResponsesClient, ResponsesClientSettings, getOutputText } from './openai/responsesClient.js';
export { createImageContentFromFile, createImageContentFromUrl } from './openai/vision.js';
export { ModelLoadManager } from './detail/modelLoadManager.js';
/** @internal */
export { CoreInterop } from './detail/coreInterop.js';
Expand Down
12 changes: 11 additions & 1 deletion sdk/js/src/openai/responsesClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import {
StreamingEvent,
InputItemsListResponse,
DeleteResponseResult,
ListResponsesResult,
ResponseInputItem,
MessageItem,
ContentPart,
Expand Down Expand Up @@ -76,7 +77,8 @@ export class ResponsesClientSettings {
tool_choice: this.toolChoice,
truncation: this.truncation,
parallel_tool_calls: this.parallelToolCalls,
store: this.store,
// Default store to true when not explicitly set
store: this.store !== undefined ? this.store : true,
Comment thread
MaanavD marked this conversation as resolved.
Outdated
metadata: this.metadata,
reasoning: this.reasoning ? filterUndefined(this.reasoning) : undefined,
text: this.text ? filterUndefined(this.text) : undefined,
Expand Down Expand Up @@ -275,6 +277,14 @@ export class ResponsesClient {
);
}

/**
* Lists all stored responses.
* @returns The list of Response objects.
*/
public async list(): Promise<ListResponsesResult> {
return this.fetchJson<ListResponsesResult>('/v1/responses', { method: 'GET' });
}
Comment thread
MaanavD marked this conversation as resolved.
Outdated

// ========================================================================
// Internal helpers
// ========================================================================
Expand Down
63 changes: 63 additions & 0 deletions sdk/js/src/openai/vision.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
// -------------------------------------------------------------------------
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
// -------------------------------------------------------------------------

import * as fs from 'fs';
import * as path from 'path';
import type { InputImageContent } from '../types.js';

const MEDIA_TYPE_MAP: Record<string, string> = {
'.png': 'image/png',
Comment thread
apsonawane marked this conversation as resolved.
Outdated
'.jpg': 'image/jpeg',
'.jpeg': 'image/jpeg',
'.gif': 'image/gif',
'.webp': 'image/webp',
};

/**
* Creates an `InputImageContent` part by reading an image file from disk.
* The file is base64-encoded and embedded directly in the content part.
*
* @param filePath - Absolute or relative path to the image file.
* @param detail - Optional detail level hint for the model ('low' | 'high' | 'auto').
* @returns An `InputImageContent` object with base64-encoded image data.
* @throws If the file extension is not a supported image format.
*/
export function createImageContentFromFile(filePath: string, detail?: 'low' | 'high' | 'auto'): InputImageContent {
Comment thread
MaanavD marked this conversation as resolved.
Outdated
const ext = path.extname(filePath).toLowerCase();
const mediaType = MEDIA_TYPE_MAP[ext];
if (!mediaType) {
throw new Error(`Unsupported image format: ${ext}. Supported formats: ${Object.keys(MEDIA_TYPE_MAP).join(', ')}`);
}

const data = fs.readFileSync(filePath);
Comment thread
MaanavD marked this conversation as resolved.
Outdated
const content: InputImageContent = {
type: 'input_image',
image_data: data.toString('base64'),
media_type: mediaType,
};
if (detail !== undefined) {
content.detail = detail;
}
return content;
}

/**
* Creates an `InputImageContent` part from a URL.
*
* @param url - Public URL pointing to the image.
* @param detail - Optional detail level hint for the model ('low' | 'high' | 'auto').
* @returns An `InputImageContent` object with the image URL.
*/
export function createImageContentFromUrl(url: string, detail?: 'low' | 'high' | 'auto'): InputImageContent {
const content: InputImageContent = {
type: 'input_image',
image_url: url,
media_type: 'image/unknown', // server will detect from URL
Comment thread
MaanavD marked this conversation as resolved.
Outdated
};
if (detail !== undefined) {
content.detail = detail;
}
return content;
}
79 changes: 78 additions & 1 deletion sdk/js/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,20 @@ export interface InputTextContent {
text: string;
}

export interface InputImageContent {
type: 'input_image';
image_url?: string;
image_data?: string; // base64-encoded
media_type: string; // e.g. "image/png"
Comment thread
MaanavD marked this conversation as resolved.
Outdated
detail?: 'low' | 'high' | 'auto';
}

export interface InputFileContent {
type: 'input_file';
filename: string;
file_url: string;
}

export interface OutputTextContent {
type: 'output_text';
text: string;
Expand All @@ -139,7 +153,7 @@ export interface RefusalContent {
refusal: string;
}

export type ContentPart = InputTextContent | OutputTextContent | RefusalContent;
export type ContentPart = InputTextContent | InputImageContent | InputFileContent | OutputTextContent | RefusalContent;

export interface Annotation {
type: string;
Expand Down Expand Up @@ -419,6 +433,55 @@ export interface FunctionCallArgsDoneEvent {
sequence_number: number;
}

export interface ReasoningSummaryPartAddedEvent {
type: 'response.reasoning_summary_part.added';
item_id: string;
part: ContentPart;
sequence_number: number;
}
Comment thread
MaanavD marked this conversation as resolved.
Outdated

export interface ReasoningSummaryPartDoneEvent {
type: 'response.reasoning_summary_part.done';
item_id: string;
part: ContentPart;
sequence_number: number;
}

export interface ReasoningDeltaEvent {
type: 'response.reasoning.delta';
item_id: string;
delta: string;
sequence_number: number;
}

export interface ReasoningDoneEvent {
type: 'response.reasoning.done';
item_id: string;
text: string;
sequence_number: number;
}

export interface ReasoningSummaryTextDeltaEvent {
type: 'response.reasoning_summary_text.delta';
item_id: string;
delta: string;
sequence_number: number;
}

export interface ReasoningSummaryTextDoneEvent {
type: 'response.reasoning_summary_text.done';
item_id: string;
text: string;
sequence_number: number;
}

export interface OutputTextAnnotationAddedEvent {
type: 'response.output_text.annotation.added';
item_id: string;
annotation: Annotation;
sequence_number: number;
}

export interface StreamingErrorEvent {
type: 'error';
code?: string;
Expand All @@ -439,4 +502,18 @@ export type StreamingEvent =
| RefusalDoneEvent
| FunctionCallArgsDeltaEvent
| FunctionCallArgsDoneEvent
| ReasoningSummaryPartAddedEvent
| ReasoningSummaryPartDoneEvent
| ReasoningDeltaEvent
| ReasoningDoneEvent
| ReasoningSummaryTextDeltaEvent
| ReasoningSummaryTextDoneEvent
| OutputTextAnnotationAddedEvent
| StreamingErrorEvent;

// --- List Responses ---

export interface ListResponsesResult {
object: 'list';
data: ResponseObject[];
}
Loading
Loading