Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
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
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,13 @@
- [terminal] fixed the file link provider logging a `UriError` when a printed URL (e.g. `https://example.com/path`) was matched as a local file candidate; such `//host/path` candidates are now skipped and left to the URL link provider [#17767](https://github.com/eclipse-theia/theia/pull/17767)
- [ai-anthropic, ai-chat, ai-chat-ui, ai-core, ai-openai] added provider-native server-side compaction for chat sessions, modeled as a model capability (`LanguageModelMetaData.serverSideCompactionSupport`) plus layered activation: a global preference (`ai-features.chat.serverSideCompaction`, default on), a per-provider override, and a per-session override (in the session settings dialog) that wins over both. Honored by the Anthropic Messages API (beta) and the OpenAI Responses API; the compaction marker is persisted in the session, replayed on subsequent requests, surfaced as an inline chat marker, and reflected in the token-usage tooltip (cumulative usage and a "compacted Nx" count). Providers/models without the capability ignore it [#17636](https://github.com/eclipse-theia/theia/issues/17636)
- [ai-chat-ui] replaced scroll-direction heuristics in `ChatViewTreeWidget` with Virtuoso's `atBottomStateChange` for reliable auto-scroll during streaming [#17728](https://github.com/eclipse-theia/theia/pull/17728)
- [ai-vercel-ai] deprecated `@theia/ai-vercel-ai` package [#TBD](https://github.com/eclipse-theia/theia/pull/TBD)
- [electron] upgraded Electron from 39.8.7 to 42.3.0 (Node 24, Chromium 148) [#17586](https://github.com/eclipse-theia/theia/pull/17586)

<a name="breaking_changes_1.74.0">[Breaking Changes:](#breaking_changes_1.74.0)</a>

- [ai-chat-ui] `ChatViewTreeWidget` scroll-lock is now driven by Virtuoso's `atBottomStateChange` callback instead of scroll-direction heuristics; the following `protected` members have been removed: `handleScrollEvent()`, `getCurrentScrollTop()`, `isAtAbsoluteBottom()`, `updateScrollToBottomButtonState()`, `lastScrollTop`, `_scrollButtonDebounceTimer`, `SCROLL_BUTTON_GRACE_PERIOD`, and the `updateScrollToRow()` override. Subclasses that override any of these should use the new `handleAtBottomStateChange(isAtBottom: boolean)` method instead [#17728](https://github.com/eclipse-theia/theia/pull/17728)
- [ai-vercel-ai] deprecated the `@theia/ai-vercel-ai` experimental extension and stopped publishing it on npm. Please use the dedicated `@theia/ai-openai` and `@theia/ai-anthropic` providers instead, which cover the same models with first-class support. [#TBD](https://github.com/eclipse-theia/theia/pull/TBD)
- [core] removed `TreeWidget.onScroll`, `TreeWidget.onScrollEmitter`, `TreeWidget.getVirtualizedScrollState()`, `TreeWidget.isScrolledToBottom()`, the `TreeScrollEvent` interface, and the `TreeScrollState` interface. Use `TreeWidget.onAtBottomStateChange` to react to bottom-state changes instead [#17728](https://github.com/eclipse-theia/theia/pull/17728)
- [electron] upgraded Electron from 39.8.7 to 42.3.0. Downstream Electron applications must update their `electron` devDependency to `42.3.0`. Electron 42 bundles Node 24 (up from Node 22), which may affect native modules or Node APIs used by downstream applications. See the Electron breaking changes for [40.0](https://www.electronjs.org/docs/latest/breaking-changes#planned-breaking-api-changes-400), [41.0](https://www.electronjs.org/docs/latest/breaking-changes#planned-breaking-api-changes-410), and [42.0](https://www.electronjs.org/docs/latest/breaking-changes#planned-breaking-api-changes-420). [#17586](https://github.com/eclipse-theia/theia/pull/17586)

Expand Down
16 changes: 16 additions & 0 deletions doc/Migration.md
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,22 @@ For example, in an `electron-builder` configuration, ensure the `lib/backend/she

The `lib/**/*` glob already covers `lib/backend/shell-integrations/`. If you use a more restrictive `files` pattern, make sure `lib/backend/shell-integrations/**/*` is explicitly included, as `ShellIntegrationInjector` resolves these scripts relative to `__dirname` (i.e. `lib/backend/`).

### v1.74.0

#### Deprecation of @theia/ai-vercel-ai package

The `@theia/ai-vercel-ai` package has been marked as deprecated and is no longer published on npm. It will be removed from the Theia codebase after a deprecation period. The package only wrapped OpenAI and Anthropic models, both of which are already covered by the dedicated `@theia/ai-openai` and `@theia/ai-anthropic` providers with first-class support.

If your application depends on `@theia/ai-vercel-ai`, migrate as follows:

- **OpenAI models**: use `@theia/ai-openai`. Move `ai-features.vercelAi.openaiApiKey` to `ai-features.openAiOfficial.openAiApiKey` and define models in `ai-features.openAiOfficial.officialOpenAiModels`.
- **Anthropic models**: use `@theia/ai-anthropic`. Move `ai-features.vercelAi.anthropicApiKey` to `ai-features.anthropic.AnthropicApiKey` and define models in `ai-features.anthropic.AnthropicModels`.
- **Custom endpoints** (`ai-features.vercelAi.customModels`): split entries by their `provider` field into the matching provider-specific preference:
- `provider: 'openai'` → `ai-features.openAiCustom.customOpenAiModels`
- `provider: 'anthropic'` → `ai-features.anthropicCustom.customAnthropicModels`

The dedicated provider preferences offer the same fields plus additional ones (e.g. Azure `apiVersion`/`deployment`, `useResponseApi`, `reasoningSupport` for OpenAI; `useCaching`, `maxRetries` for Anthropic).

### v1.73.0

#### Custom agents reorganized into per-agent folders [#17523](https://github.com/eclipse-theia/theia/pull/17523)
Expand Down
1 change: 0 additions & 1 deletion examples/browser/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,6 @@
"@theia/ai-registry": "1.73.0",
"@theia/ai-scanoss": "1.73.0",
"@theia/ai-terminal": "1.73.0",
"@theia/ai-vercel-ai": "1.73.0",
"@theia/api-provider-sample": "1.73.0",
"@theia/api-samples": "1.73.0",
"@theia/bulk-edit": "1.73.0",
Expand Down
3 changes: 0 additions & 3 deletions examples/browser/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -77,9 +77,6 @@
{
"path": "../../packages/ai-terminal"
},
{
"path": "../../packages/ai-vercel-ai"
},
{
"path": "../../packages/bulk-edit"
},
Expand Down
1 change: 0 additions & 1 deletion examples/electron/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,6 @@
"@theia/ai-registry": "1.73.0",
"@theia/ai-scanoss": "1.73.0",
"@theia/ai-terminal": "1.73.0",
"@theia/ai-vercel-ai": "1.73.0",
"@theia/api-provider-sample": "1.73.0",
"@theia/api-samples": "1.73.0",
"@theia/bulk-edit": "1.73.0",
Expand Down
3 changes: 0 additions & 3 deletions examples/electron/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -80,9 +80,6 @@
{
"path": "../../packages/ai-terminal"
},
{
"path": "../../packages/ai-vercel-ai"
},
{
"path": "../../packages/bulk-edit"
},
Expand Down
2 changes: 0 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

8 changes: 2 additions & 6 deletions packages/ai-openai/src/common/openai-preferences.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,12 +53,8 @@ on the machine running Theia. Use the environment variable `OPENAI_API_KEY` to s
default: false,
title: AI_CORE_PREFERENCES_TITLE,
markdownDescription: nls.localize('theia/ai/openai/useResponseApi/mdDescription',
'Use the newer OpenAI Response API instead of the Chat Completion API for official OpenAI models.\
\
This setting only applies to official OpenAI models - custom providers must configure this individually.\
\
Note that for the response API, tool call definitions must satisfy Open AI\'s [strict schema definition](https://platform.openai.com/docs/guides/function-calling#strict-mode).\
Best effort is made to convert non-conformant schemas, but errors are still possible.')
'Use the newer OpenAI Response API instead of the Chat Completion API for official OpenAI models. ' +
'This setting only applies to official OpenAI models - custom providers must configure this individually.')
},
[SERVER_SIDE_COMPACTION_PREF]: {
type: 'string',
Expand Down
104 changes: 1 addition & 103 deletions packages/ai-openai/src/node/openai-model-utils.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,7 @@
import { expect } from 'chai';
import { OpenAiModelUtils } from './openai-language-model';
import { LanguageModelMessage } from '@theia/ai-core';
import { OpenAiResponseApiUtils, recursiveStrictJSONSchema } from './openai-response-api-utils';
import type { JSONSchema, JSONSchemaDefinition } from 'openai/lib/jsonschema';
import { OpenAiResponseApiUtils } from './openai-response-api-utils';

const utils = new OpenAiModelUtils();
const responseUtils = new OpenAiResponseApiUtils();
Expand Down Expand Up @@ -495,105 +494,4 @@ describe('OpenAiModelUtils - processMessagesForResponseApi', () => {
});
});

describe('recursiveStrictJSONSchema', () => {
it('should return the same object and not modify it when schema has no properties to strictify', () => {
const schema: JSONSchema = { type: 'string', description: 'Simple string' };
const originalJson = JSON.stringify(schema);

const result = recursiveStrictJSONSchema(schema);

expect(result).to.equal(schema);
expect(JSON.stringify(schema)).to.equal(originalJson);
const resultObj = result as JSONSchema;
expect(resultObj).to.not.have.property('additionalProperties');
expect(resultObj).to.not.have.property('required');
});

it('should not mutate original but return a new strictified schema when branching applies (properties/items)', () => {
const original: JSONSchema = {
type: 'object',
properties: {
path: { type: 'string' },
data: {
type: 'array',
items: {
type: 'object',
properties: {
a: { type: 'string' }
}
}
}
}
};
const originalClone = JSON.parse(JSON.stringify(original));

const resultDef = recursiveStrictJSONSchema(original);
const result = resultDef as JSONSchema;

expect(result).to.not.equal(original);
expect(original).to.deep.equal(originalClone);

expect(result.additionalProperties).to.equal(false);
expect(result.required).to.have.members(['path', 'data']);

const itemsDef = (result.properties?.data as JSONSchema).items as JSONSchemaDefinition;
expect(itemsDef).to.be.ok;
const itemsObj = itemsDef as JSONSchema;
expect(itemsObj.additionalProperties).to.equal(false);
expect(itemsObj.required).to.have.members(['a']);

const originalItems = ((original.properties!.data as JSONSchema).items) as JSONSchema;
expect(originalItems).to.not.have.property('additionalProperties');
expect(originalItems).to.not.have.property('required');
});

it('should strictify nested parameters schema and not mutate the original', () => {
const replacementProperties: Record<string, JSONSchema> = {
oldContent: { type: 'string', description: 'The exact content to be replaced. Must match exactly, including whitespace, comments, etc.' },
newContent: { type: 'string', description: 'The new content to insert in place of matched old content.' },
multiple: { type: 'boolean', description: 'Set to true if multiple occurrences of the oldContent are expected to be replaced.' }
};

const parameters: JSONSchema = {
type: 'object',
properties: {
path: { type: 'string', description: 'The path of the file where content will be replaced.' },
replacements: {
type: 'array',
items: {
type: 'object',
properties: replacementProperties,
required: ['oldContent', 'newContent']
},
description: 'An array of replacement objects, each containing oldContent and newContent strings.'
},
reset: {
type: 'boolean',
description: 'Set to true to clear any existing pending changes for this file and start fresh. Default is false, which merges with existing changes.'
}
},
required: ['path', 'replacements']
};

const originalClone = JSON.parse(JSON.stringify(parameters));

const strictifiedDef = recursiveStrictJSONSchema(parameters);
const strictified = strictifiedDef as JSONSchema;

expect(strictified).to.not.equal(parameters);
expect(parameters).to.deep.equal(originalClone);

expect(strictified.additionalProperties).to.equal(false);
expect(strictified.required).to.have.members(['path', 'replacements', 'reset']);

const items = (strictified.properties!.replacements as JSONSchema).items as JSONSchema;
expect(items.additionalProperties).to.equal(false);
expect(items.required).to.have.members(['oldContent', 'newContent', 'multiple']);

const origItems = ((parameters.properties!.replacements as JSONSchema).items) as JSONSchema;
expect(origItems.required).to.deep.equal(['oldContent', 'newContent']);
expect(origItems).to.not.have.property('additionalProperties');
});
});

});
33 changes: 33 additions & 0 deletions packages/ai-openai/src/node/openai-response-api-utils.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,39 @@ async function* toStream(events: unknown[]): AsyncIterable<unknown> {
}

describe('OpenAiResponseApiUtils', () => {
it('passes tool parameters through unchanged in non-strict mode', () => {
const utils = new OpenAiResponseApiUtils();
const parameters = {
type: 'object' as const,
properties: {
metadata: {
type: 'object' as const,
additionalProperties: { type: 'string' }
}
}
};

const converted = utils.convertToolsForResponseApi([{
id: 'lookup',
name: 'lookup',
parameters,
handler: async () => 'result'
}]);

expect(converted).to.have.lengthOf(1);
const convertedTool = converted?.[0];
expect(convertedTool).to.include({
type: 'function',
name: 'lookup',
parameters,
strict: false
});
if (convertedTool?.type !== 'function') {
throw new Error('Expected a function tool');
}
expect(convertedTool.parameters).to.equal(parameters);
});

it('emits per-iteration usage for Response API tool calls instead of accumulated usage', async () => {
const utils = new OpenAiResponseApiUtils();
const streams = [
Expand Down
63 changes: 5 additions & 58 deletions packages/ai-openai/src/node/openai-response-api-utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,6 @@ import {
TextMessage,
ToolInvocationContext,
ToolRequest,
ToolRequestParameters,
UserRequest
} from '@theia/ai-core';
import { CancellationToken, nls, unreachable } from '@theia/core';
Expand All @@ -44,7 +43,6 @@ import type {
} from 'openai/resources/responses/responses';
import type { ResponsesModel } from 'openai/resources/shared';
import { DeveloperMessageSettings, OpenAiModelUtils } from './openai-language-model';
import { JSONSchema, JSONSchemaDefinition } from 'openai/lib/jsonschema';

/**
* User-facing name under which the provider's built-in deferred-tool search is surfaced in the chat UI.
Expand Down Expand Up @@ -159,11 +157,11 @@ export class OpenAiResponseApiUtils {
type: 'function' as const,
name: tool.name,
description: tool.description || '',
// The Response API is very strict re: JSON schema: all properties must be listed as required,
// and additional properties must be disallowed.
// https://platform.openai.com/docs/guides/function-calling#strict-mode
parameters: this.recursiveStrictToolCallParameters(tool.parameters),
strict: true,
// Tool schemas are passed through as-is (non-strict). Strict mode (`strict: true`) is opt-in and
// only supports a JSON-schema subset (all properties required, `additionalProperties: false`
// everywhere), which cannot express the open-ended maps common in MCP tool schemas.
parameters: tool.parameters as unknown as FunctionTool['parameters'],
strict: false,
defer_loading: deferred.has(tool.id) ? true : undefined
}));
if (deferred.size > 0) {
Expand All @@ -173,10 +171,6 @@ export class OpenAiResponseApiUtils {
return converted;
}

recursiveStrictToolCallParameters(schema: ToolRequestParameters): FunctionTool['parameters'] {
return recursiveStrictJSONSchema(schema) as FunctionTool['parameters'];
}

protected createSimpleResponseApiStreamIterator(
stream: AsyncIterable<ResponseStreamEvent>,
cancellationToken?: CancellationToken
Expand Down Expand Up @@ -883,50 +877,3 @@ export function processSystemMessages(
}
return messages;
}

export function recursiveStrictJSONSchema(schema: JSONSchemaDefinition): JSONSchemaDefinition {
if (typeof schema === 'boolean') { return schema; }
let result: JSONSchema | undefined = undefined;
if (schema.properties) {
result ??= { ...schema };
result.additionalProperties = false;
result.required = Object.keys(schema.properties);
result.properties = Object.fromEntries(Object.entries(schema.properties).map(([key, props]) => [key, recursiveStrictJSONSchema(props)]));
}
if (schema.items) {
result ??= { ...schema };
result.items = Array.isArray(schema.items)
? schema.items.map(recursiveStrictJSONSchema)
: recursiveStrictJSONSchema(schema.items);
}
if (schema.oneOf) {
result ??= { ...schema };
result.oneOf = schema.oneOf.map(recursiveStrictJSONSchema);
}
if (schema.anyOf) {
result ??= { ...schema };
result.anyOf = schema.anyOf.map(recursiveStrictJSONSchema);
}
if (schema.allOf) {
result ??= { ...schema };
result.allOf = schema.allOf.map(recursiveStrictJSONSchema);
}
if (schema.if) {
result ??= { ...schema };
result.if = recursiveStrictJSONSchema(schema.if);
}
if (schema.then) {
result ??= { ...schema };
result.then = recursiveStrictJSONSchema(schema.then);
}
if (schema.else) {
result ??= { ...schema };
result.else = recursiveStrictJSONSchema(schema.else);
}
if (schema.not) {
result ??= { ...schema };
result.not = recursiveStrictJSONSchema(schema.not);
}

return result ?? schema;
}
1 change: 1 addition & 0 deletions packages/ai-vercel-ai/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
<img src='https://raw.githubusercontent.com/eclipse-theia/theia/master/logo/theia.svg?sanitize=true' alt='theia-ext-logo' width='100px' />

<h2>ECLIPSE THEIA - VERCEL AI SDK INTEGRATION</h2>
<h2>This extension is DEPRECATED!</h2>

<hr />

Expand Down
1 change: 1 addition & 0 deletions packages/ai-vercel-ai/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
"name": "@theia/ai-vercel-ai",
"version": "1.73.0",
"description": "Theia - Vercel AI SDK Integration",
"private": true,
"dependencies": {
"@ai-sdk/anthropic": "^1.2.12",
"@ai-sdk/openai": "^1.3.24",
Expand Down
Loading