Skip to content

Commit 76f1859

Browse files
Add initial LLM model support
This creates a way to have a default llm provider so plugins can make simple inference. This was assisted by Claude Opus 4.7 Signed-off-by: Matthew Khouzam <matthew.khouzam@ericsson.com>
1 parent 9e8a07f commit 76f1859

14 files changed

Lines changed: 501 additions & 0 deletions

File tree

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<classpath>
3+
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-21"/>
4+
<classpathentry kind="con" path="org.eclipse.pde.core.requiredPlugins"/>
5+
<classpathentry kind="src" path="src"/>
6+
<classpathentry kind="output" path="bin"/>
7+
</classpath>
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<projectDescription>
3+
<name>org.eclipse.core.llm</name>
4+
<comment></comment>
5+
<projects>
6+
</projects>
7+
<buildSpec>
8+
<buildCommand>
9+
<name>org.eclipse.jdt.core.javabuilder</name>
10+
<arguments>
11+
</arguments>
12+
</buildCommand>
13+
<buildCommand>
14+
<name>org.eclipse.pde.ManifestBuilder</name>
15+
<arguments>
16+
</arguments>
17+
</buildCommand>
18+
<buildCommand>
19+
<name>org.eclipse.pde.SchemaBuilder</name>
20+
<arguments>
21+
</arguments>
22+
</buildCommand>
23+
</buildSpec>
24+
<natures>
25+
<nature>org.eclipse.pde.PluginNature</nature>
26+
<nature>org.eclipse.jdt.core.javanature</nature>
27+
</natures>
28+
</projectDescription>
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
Manifest-Version: 1.0
2+
Bundle-ManifestVersion: 2
3+
Bundle-Name: Core LLM
4+
Bundle-SymbolicName: org.eclipse.core.llm;singleton:=true
5+
Bundle-Version: 1.0.0.qualifier
6+
Bundle-Vendor: Eclipse.org
7+
Export-Package: org.eclipse.core.llm
8+
Require-Bundle: org.eclipse.swt,
9+
org.eclipse.jface
10+
Bundle-RequiredExecutionEnvironment: JavaSE-21
11+
Automatic-Module-Name: org.eclipse.core.llm
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
2+
<html>
3+
<head>
4+
<meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1">
5+
<title>About</title>
6+
</head>
7+
<body lang="EN-US">
8+
<h2>About This Content</h2>
9+
<p>The Eclipse Foundation makes available all content in this plug-in
10+
("Content"). Unless otherwise indicated below, the Content is provided
11+
to you under the terms and conditions of the Eclipse Public License
12+
Version 2.0 ("EPL"). A copy of the EPL is available at
13+
<a href="https://www.eclipse.org/legal/epl-2.0/">https://www.eclipse.org/legal/epl-2.0/</a>.</p>
14+
</body>
15+
</html>
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
source.. = src/
2+
output.. = bin/
3+
bin.includes = META-INF/,\
4+
.,\
5+
about.html
6+
src.includes = about.html
Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
/*******************************************************************************
2+
* Copyright (c) 2026 Ericsson
3+
*
4+
* This program and the accompanying materials
5+
* are made available under the terms of the Eclipse Public License 2.0
6+
* which accompanies this distribution, and is available at
7+
* https://www.eclipse.org/legal/epl-2.0/
8+
*
9+
* SPDX-License-Identifier: EPL-2.0
10+
*******************************************************************************/
11+
package org.eclipse.core.llm;
12+
13+
import org.eclipse.jface.dialogs.Dialog;
14+
import org.eclipse.jface.dialogs.IDialogConstants;
15+
import org.eclipse.swt.SWT;
16+
import org.eclipse.swt.layout.GridData;
17+
import org.eclipse.swt.layout.GridLayout;
18+
import org.eclipse.swt.widgets.Composite;
19+
import org.eclipse.swt.widgets.Control;
20+
import org.eclipse.swt.widgets.Label;
21+
import org.eclipse.swt.widgets.Shell;
22+
import org.eclipse.swt.widgets.Text;
23+
24+
/**
25+
* Simple JFace dialog to configure the URL and model name of a {@link LlmModel}.
26+
* Retrieve the result with {@link #getModel()} after {@link #open()} returns
27+
* {@link org.eclipse.jface.window.Window#OK}.
28+
*/
29+
public class LlmConfigurationDialog extends Dialog {
30+
31+
private String url;
32+
private String model;
33+
private Text urlText;
34+
private Text modelText;
35+
private LlmModel result;
36+
37+
public LlmConfigurationDialog(Shell parent, LlmModel initial) {
38+
super(parent);
39+
this.url = initial != null ? initial.url() : ""; //$NON-NLS-1$
40+
this.model = initial != null ? initial.model() : ""; //$NON-NLS-1$
41+
}
42+
43+
public LlmModel getModel() {
44+
return result;
45+
}
46+
47+
@Override
48+
protected void configureShell(Shell shell) {
49+
super.configureShell(shell);
50+
shell.setText("LLM Configuration"); //$NON-NLS-1$
51+
}
52+
53+
@Override
54+
protected Control createDialogArea(Composite parent) {
55+
Composite area = (Composite) super.createDialogArea(parent);
56+
Composite c = new Composite(area, SWT.NONE);
57+
c.setLayout(new GridLayout(2, false));
58+
c.setLayoutData(new GridData(GridData.FILL_BOTH));
59+
60+
new Label(c, SWT.NONE).setText("URL:"); //$NON-NLS-1$
61+
urlText = new Text(c, SWT.BORDER);
62+
urlText.setText(url);
63+
GridData gd = new GridData(GridData.FILL_HORIZONTAL);
64+
gd.widthHint = 320;
65+
urlText.setLayoutData(gd);
66+
67+
new Label(c, SWT.NONE).setText("Model:"); //$NON-NLS-1$
68+
modelText = new Text(c, SWT.BORDER);
69+
modelText.setText(model);
70+
modelText.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
71+
72+
return area;
73+
}
74+
75+
@Override
76+
protected void buttonPressed(int buttonId) {
77+
if (buttonId == IDialogConstants.OK_ID) {
78+
result = new LlmModel(urlText.getText().trim(), modelText.getText().trim());
79+
}
80+
super.buttonPressed(buttonId);
81+
}
82+
}
Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
/*******************************************************************************
2+
* Copyright (c) 2026 Ericsson
3+
*
4+
* This program and the accompanying materials
5+
* are made available under the terms of the Eclipse Public License 2.0
6+
* which accompanies this distribution, and is available at
7+
* https://www.eclipse.org/legal/epl-2.0/
8+
*
9+
* SPDX-License-Identifier: EPL-2.0
10+
*******************************************************************************/
11+
package org.eclipse.core.llm;
12+
13+
import java.io.IOException;
14+
import java.net.URI;
15+
import java.net.http.HttpClient;
16+
import java.net.http.HttpRequest;
17+
import java.net.http.HttpResponse;
18+
import java.time.Duration;
19+
20+
import org.eclipse.swt.widgets.Display;
21+
22+
/**
23+
* Minimal LLM client. An instance is configured with the endpoint URL and the
24+
* model name and exposes {@link #infer(String)} to query the model.
25+
* <p>
26+
* {@link #infer(String)} performs a blocking HTTP request and must never be
27+
* invoked from the SWT UI thread; doing so throws {@link IllegalStateException}.
28+
* </p>
29+
*/
30+
public final class LlmModel {
31+
32+
private final String url;
33+
private final String model;
34+
35+
public LlmModel(String url, String model) {
36+
this.url = url;
37+
this.model = model;
38+
}
39+
40+
public String url() {
41+
return url;
42+
}
43+
44+
public String model() {
45+
return model;
46+
}
47+
48+
/**
49+
* Send {@code prompt} to the configured LLM and return the raw response body.
50+
*
51+
* @throws IllegalStateException if invoked from the SWT UI thread
52+
* @throws IOException if the HTTP call fails
53+
*/
54+
public String infer(String prompt) throws IOException, InterruptedException {
55+
if (Display.getCurrent() != null) {
56+
throw new IllegalStateException("LlmModel.infer must not be called from the UI thread"); //$NON-NLS-1$
57+
}
58+
String body = "{\"model\":\"" + escape(model) + "\",\"prompt\":\"" + escape(prompt) + "\",\"stream\":false}"; //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
59+
HttpRequest request = HttpRequest.newBuilder(URI.create(url))
60+
.timeout(Duration.ofMinutes(2))
61+
.header("Content-Type", "application/json") //$NON-NLS-1$ //$NON-NLS-2$
62+
.POST(HttpRequest.BodyPublishers.ofString(body))
63+
.build();
64+
HttpResponse<String> response = HttpClient.newHttpClient()
65+
.send(request, HttpResponse.BodyHandlers.ofString());
66+
if (response.statusCode() / 100 != 2) {
67+
throw new IOException("LLM request failed: HTTP " + response.statusCode() + " - " + response.body()); //$NON-NLS-1$ //$NON-NLS-2$
68+
}
69+
return response.body();
70+
}
71+
72+
private static String escape(String s) {
73+
StringBuilder sb = new StringBuilder(s.length() + 8);
74+
for (int i = 0; i < s.length(); i++) {
75+
char c = s.charAt(i);
76+
switch (c) {
77+
case '"' -> sb.append("\\\""); //$NON-NLS-1$
78+
case '\\' -> sb.append("\\\\"); //$NON-NLS-1$
79+
case '\n' -> sb.append("\\n"); //$NON-NLS-1$
80+
case '\r' -> sb.append("\\r"); //$NON-NLS-1$
81+
case '\t' -> sb.append("\\t"); //$NON-NLS-1$
82+
default -> {
83+
if (c < 0x20) {
84+
sb.append(String.format("\\u%04x", (int) c)); //$NON-NLS-1$
85+
} else {
86+
sb.append(c);
87+
}
88+
}
89+
}
90+
}
91+
return sb.toString();
92+
}
93+
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<classpath>
3+
<classpathentry kind="src" path="src"/>
4+
<classpathentry kind="con" path="org.eclipse.pde.core.requiredPlugins"/>
5+
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-21"/>
6+
<classpathentry kind="output" path="bin"/>
7+
</classpath>
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<projectDescription>
3+
<name>org.eclipse.core.llm.tests</name>
4+
<comment></comment>
5+
<projects>
6+
</projects>
7+
<buildSpec>
8+
<buildCommand>
9+
<name>org.eclipse.jdt.core.javabuilder</name>
10+
<arguments>
11+
</arguments>
12+
</buildCommand>
13+
<buildCommand>
14+
<name>org.eclipse.pde.ManifestBuilder</name>
15+
<arguments>
16+
</arguments>
17+
</buildCommand>
18+
<buildCommand>
19+
<name>org.eclipse.pde.SchemaBuilder</name>
20+
<arguments>
21+
</arguments>
22+
</buildCommand>
23+
</buildSpec>
24+
<natures>
25+
<nature>org.eclipse.jdt.core.javanature</nature>
26+
<nature>org.eclipse.pde.PluginNature</nature>
27+
</natures>
28+
</projectDescription>
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
Manifest-Version: 1.0
2+
Bundle-ManifestVersion: 2
3+
Bundle-Name: Core LLM Tests
4+
Bundle-SymbolicName: org.eclipse.core.llm.tests;singleton:=true
5+
Bundle-Version: 1.0.0.qualifier
6+
Bundle-Vendor: Eclipse.org
7+
Require-Bundle: org.eclipse.core.llm,
8+
org.eclipse.swt,
9+
org.eclipse.jface
10+
Import-Package: com.sun.net.httpserver,
11+
org.junit.jupiter.api;version="[5.14.0,6.0.0)"
12+
Bundle-RequiredExecutionEnvironment: JavaSE-21
13+
Bundle-ActivationPolicy: lazy
14+
Automatic-Module-Name: org.eclipse.core.llm.tests

0 commit comments

Comments
 (0)