Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add teams runtime #1095

Merged
merged 18 commits into from
Feb 5, 2025
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
1 change: 1 addition & 0 deletions docs/src/content/docs/reference/cli/commands.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ Options:
-prc, --pull-request-comment [string] create comment on a pull request with a unique id (defaults to script id)
-prd, --pull-request-description [string] create comment on a pull request description with a unique id (defaults to script id)
-prr, --pull-request-reviews create pull request reviews from annotations
-tm, --teams-message Posts a message to the teams channel
pelikhan marked this conversation as resolved.
Show resolved Hide resolved
-j, --json emit full JSON response to output
-y, --yaml emit full YAML response to output
-fe, --fail-on-errors fails on detected annotation error
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@
"genai:docify": "node packages/cli/built/genaiscript.cjs run docify",
"gcm": "node packages/cli/built/genaiscript.cjs run gcm --model github:gpt-4o",
"prd": "node packages/cli/built/genaiscript.cjs run prd -prd --model github:gpt-4o",
"prr": "node packages/cli/built/genaiscript.cjs run prr -prc --model github:gpt-4o",
"prr": "node packages/cli/built/genaiscript.cjs run prr -prr --model github:gpt-4o-mini",
"genai": "node packages/cli/built/genaiscript.cjs run",
"genai:convert": "node packages/cli/built/genaiscript.cjs convert",
"genai:debug": "yarn compile-debug && node packages/cli/built/genaiscript.cjs run",
Expand Down
4 changes: 4 additions & 0 deletions packages/cli/src/cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,10 @@ export async function cli() {
"-prr, --pull-request-reviews",
"create pull request reviews from annotations"
)
.option(
"-tm, --teams-message",
"Posts a message to the teams channel"
)
.option("-j, --json", "emit full JSON response to output")
.option("-y, --yaml", "emit full YAML response to output")
.option(`-fe, --fail-on-errors`, `fails on detected annotation error`)
Expand Down
6 changes: 6 additions & 0 deletions packages/cli/src/nodehost.ts
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,7 @@ export class NodeHost implements RuntimeHost {
readonly userInputQueue = new PLimitPromiseQueue(1)
readonly azureToken: AzureTokenResolver
readonly azureServerlessToken: AzureTokenResolver
readonly microsoftGraphToken: AzureTokenResolver

constructor(dotEnvPath: string) {
this.dotEnvPath = dotEnvPath
Expand All @@ -110,6 +111,11 @@ export class NodeHost implements RuntimeHost {
"AZURE_SERVERLESS_OPENAI_TOKEN_SCOPES",
AZURE_AI_INFERENCE_TOKEN_SCOPES
)
this.microsoftGraphToken = createAzureTokenResolver(
"Microsoft Graph",
"MICROSOFT_GRAPH_TOKEN_SCOPES",
["https://graph.microsoft.com/.default"]
)
}

get modelAliases(): Readonly<ModelConfigurations> {
Expand Down
17 changes: 17 additions & 0 deletions packages/cli/src/run.ts
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,7 @@ import { parsePromptScriptMeta } from "../../core/src/template"
import { Fragment } from "../../core/src/generation"
import { randomHex } from "../../core/src/crypto"
import { normalizeFloat, normalizeInt } from "../../core/src/cleaners"
import { microsoftTeamsChannelPostMessage } from "../../core/src/teams"

function getRunDir(scriptId: string) {
const runId =
Expand Down Expand Up @@ -180,6 +181,7 @@ export async function runScriptInternal(
const pullRequestComment = options.pullRequestComment
const pullRequestDescription = options.pullRequestDescription
const pullRequestReviews = options.pullRequestReviews
const teamsMessage = options.teamsMessage
const outData = options.outData
const label = options.label
const temperature = normalizeFloat(options.temperature)
Expand Down Expand Up @@ -524,6 +526,21 @@ export async function runScriptInternal(
}
let adoInfo: AzureDevOpsEnv = undefined

if (teamsMessage && result.text) {
const ghInfo = await resolveGitHubInfo()
const channelURL =
process.env.GENAISCRIPT_TEAMS_CHANNEL_URL ||
process.env.TEAMS_CHANNEL_URL
if (channelURL) {
await microsoftTeamsChannelPostMessage(channelURL, result.text, {
script,
info: ghInfo,
cancellationToken,
trace,
})
}
}

if (pullRequestReviews && result.annotations?.length) {
// github action or repo
const ghInfo = await resolveGitHubInfo()
Expand Down
1 change: 1 addition & 0 deletions packages/cli/src/runtime.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import { delay, uniq, uniqBy, chunk, groupBy } from "es-toolkit"
import { z } from "zod"
import { pipeline } from "@huggingface/transformers"
import { readFile } from "fs/promises"

// symbols exported as is
export { delay, uniq, uniqBy, z, pipeline, chunk, groupBy }
Expand Down
8 changes: 7 additions & 1 deletion packages/core/src/file.ts
Original file line number Diff line number Diff line change
Expand Up @@ -196,9 +196,15 @@ ${CSVToMarkdown(tidyData(rows, options))}
* @returns The Data URI string or undefined if the MIME type cannot be determined.
*/
export async function resolveFileBytes(
filename: string,
filename: string | WorkspaceFile,
options?: TraceOptions
): Promise<Uint8Array> {
if (typeof filename === "object") {
if (filename.encoding === "base64" && filename.content)
return fromBase64(filename.content)
filename = filename.filename
}

if (/^data:/i.test(filename)) {
const matches = filename.match(/^data:[^;]+;base64,(.*)$/i)
if (!matches) throw new Error("Invalid data URI format")
Expand Down
4 changes: 4 additions & 0 deletions packages/core/src/host.ts
Original file line number Diff line number Diff line change
Expand Up @@ -128,7 +128,11 @@ export interface Host {
export interface RuntimeHost extends Host {
project: Project
workspace: Omit<WorkspaceFileSystem, "grep" | "writeCached">

azureToken: AzureTokenResolver
azureServerlessToken: AzureTokenResolver
microsoftGraphToken: AzureTokenResolver

modelAliases: Readonly<ModelConfigurations>
clientLanguageModel?: LanguageModel

Expand Down
2 changes: 2 additions & 0 deletions packages/core/src/promptcontext.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ import { DOCS_WEB_SEARCH_URL } from "./constants"
import { fetch, fetchText } from "./fetch"
import { fileWriteCached } from "./filecache"
import { join } from "node:path"
import { createMicrosoftTeamsChannelClient } from "./teams"

/**
* Creates a prompt context for the given project, variables, trace, options, and model.
Expand Down Expand Up @@ -304,6 +305,7 @@ export async function createPromptContext(
}),
python: async (options) =>
await runtimeHost.python({ trace, ...(options || {}) }),
teamsChannel: async (url) => createMicrosoftTeamsChannelClient(url),
})

// Freeze project options to prevent modification
Expand Down
1 change: 1 addition & 0 deletions packages/core/src/server/messages.ts
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,7 @@ export interface PromptScriptRunOptions {
pullRequestComment: string | boolean
pullRequestDescription: string | boolean
pullRequestReviews: boolean
teamsMessage: boolean
outData: string
label: string
temperature: string | number
Expand Down
72 changes: 72 additions & 0 deletions packages/core/src/teams.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
import { convertMarkdownToTeamsHTML } from "./teams"
import { describe, test } from "node:test"
import assert from "node:assert/strict"

describe("convertMarkdownToTeamsHTML", () => {
test("converts headers correctly", () => {
const markdown =
"# Subject\n## Heading 1\n### Heading 2\n#### Heading 3"
const result = convertMarkdownToTeamsHTML(markdown)
assert.strictEqual(result.subject, "Subject")
assert.strictEqual(
result.content,
"<div>\n<h1>Heading 1</h1>\n<h2>Heading 2</h2>\n<h3>Heading 3</h3></div>"
)
})

test("converts bold, italic, code, and strike correctly", () => {
const markdown = "**bold** *italic* `code` ~~strike~~"
const result = convertMarkdownToTeamsHTML(markdown)
assert.strictEqual(
result.content,
"<div><b>bold</b> <i>italic</i> <code>code</code> <strike>strike</strike></div>"
)
})

test("converts blockquotes correctly", () => {
const markdown = "> This is a blockquote"
const result = convertMarkdownToTeamsHTML(markdown)
assert.strictEqual(
result.content,
"<div><blockquote>This is a blockquote</blockquote>\n</div>"
)
})
test("handles empty markdown string", () => {
const markdown = ""
const result = convertMarkdownToTeamsHTML(markdown)
assert.strictEqual(result.content, "<div></div>")
assert.strictEqual(result.subject, undefined)
})

test("handles markdown without subject", () => {
const markdown = "## Heading 1\nContent"
const result = convertMarkdownToTeamsHTML(markdown)
assert.strictEqual(result.subject, undefined)
assert.strictEqual(result.content, "<div><h1>Heading 1</h1>\nContent</div>")
})
test("converts unordered lists correctly", () => {
const markdown = "- Item 1\n- Item 2\n- Item 3"
const result = convertMarkdownToTeamsHTML(markdown)
assert.strictEqual(
result.content,
"<div><br/>- Item 1\n<br/>- Item 2\n<br/>- Item 3</div>"
)
})

test("converts mixed content correctly", () => {
const markdown =
"# Subject\n## Heading 1\nContent with **bold**, *italic*, `code`, and ~~strike~~.\n- List item 1\n- List item 2\n> Blockquote"
const result = convertMarkdownToTeamsHTML(markdown)
assert.strictEqual(result.subject, "Subject")
assert.strictEqual(
result.content,
"<div>\n<h1>Heading 1</h1>\nContent with <b>bold</b>, <i>italic</i>, <code>code</code>, and <strike>strike</strike>.\n<br/>- List item 1\n<br/>- List item 2\n<blockquote>Blockquote</blockquote>\n</div>"
)
})

test("converts multiple paragraphs correctly", () => {
const markdown = "Paragraph 1\n\nParagraph 2"
const result = convertMarkdownToTeamsHTML(markdown)
assert.strictEqual(result.content, "<div>Paragraph 1\n\nParagraph 2</div>")
})
})
Loading
Loading